radix_engine/system/system_modules/execution_trace/
module.rs

1use crate::blueprints::resource::*;
2use crate::errors::*;
3use crate::internal_prelude::*;
4use crate::kernel::call_frame::CallFrameMessage;
5use crate::kernel::kernel_api::*;
6use crate::kernel::kernel_callback_api::*;
7use crate::system::actor::{Actor, FunctionActor, MethodActor};
8use crate::system::module::*;
9use crate::system::system_callback::*;
10use crate::system::system_callback_api::SystemCallbackObject;
11use crate::system::type_info::TypeInfoSubstate;
12use crate::transaction::{FeeLocks, TransactionExecutionTrace};
13use radix_common::math::Decimal;
14use radix_engine_interface::blueprints::resource::*;
15use sbor::rust::collections::*;
16use sbor::rust::fmt::Debug;
17
18//===================================================================================
19// Note: ExecutionTrace must not produce any error or transactional side effect!
20//===================================================================================
21
22// TODO: Handle potential Decimal arithmetic operation (checked_add, checked_sub) errors instead of panicking.
23// ATM, ExecutionTrace cannot return any errors (as stated above), so it shall be thoroughly
24// designed.
25
26#[derive(Debug, Clone)]
27pub struct ExecutionTraceModule {
28    /// Maximum depth up to which kernel calls are being traced.
29    max_kernel_call_depth_traced: usize,
30
31    /// Current transaction index
32    current_instruction_index: usize,
33
34    /// Current kernel calls depth. Note that this doesn't necessarily correspond to the
35    /// call frame depth, as there can be nested kernel calls within a single call frame
36    /// (e.g. open_substate call inside drop_node).
37    current_kernel_call_depth: usize,
38
39    /// A stack of traced kernel call inputs, their origin, and the instruction index.
40    traced_kernel_call_inputs_stack: Vec<(ResourceSummary, TraceOrigin, usize)>,
41
42    /// A mapping of complete KernelCallTrace stacks (\w both inputs and outputs), indexed by depth.
43    kernel_call_traces_stacks: IndexMap<usize, Vec<ExecutionTrace>>,
44
45    /// Vault operations: (Caller, Vault ID, operation, instruction index)
46    vault_ops: Vec<(TraceActor, NodeId, VaultOp, usize)>,
47}
48
49impl ExecutionTraceModule {
50    pub fn update_instruction_index(&mut self, new_index: usize) {
51        self.current_instruction_index = new_index;
52    }
53}
54
55#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)]
56pub struct ResourceChange {
57    pub node_id: NodeId,
58    pub vault_id: NodeId,
59    pub resource_address: ResourceAddress,
60    pub amount: Decimal,
61}
62
63#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)]
64pub enum WorktopChange {
65    Take(ResourceSpecifier),
66    Put(ResourceSpecifier),
67}
68
69#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)]
70pub enum ResourceSpecifier {
71    Amount(ResourceAddress, Decimal),
72    Ids(ResourceAddress, IndexSet<NonFungibleLocalId>),
73}
74
75impl From<&BucketSnapshot> for ResourceSpecifier {
76    fn from(value: &BucketSnapshot) -> Self {
77        match value {
78            BucketSnapshot::Fungible {
79                resource_address,
80                liquid,
81                ..
82            } => Self::Amount(*resource_address, *liquid),
83            BucketSnapshot::NonFungible {
84                resource_address,
85                liquid,
86                ..
87            } => Self::Ids(*resource_address, liquid.clone()),
88        }
89    }
90}
91
92#[derive(Debug, Clone)]
93pub enum VaultOp {
94    Put(ResourceAddress, Decimal), // TODO: add non-fungible support
95    Take(ResourceAddress, Decimal),
96    TakeAdvanced(ResourceAddress, Decimal),
97    Recall(ResourceAddress, Decimal),
98    LockFee(Decimal, bool),
99}
100
101trait SystemModuleApiResourceSnapshotExtension {
102    fn read_bucket_uncosted(&self, bucket_id: &NodeId) -> Option<BucketSnapshot>;
103    fn read_proof_uncosted(&self, proof_id: &NodeId) -> Option<ProofSnapshot>;
104}
105
106impl<'a, V: SystemCallbackObject, K: KernelInternalApi<System = System<V>>>
107    SystemModuleApiResourceSnapshotExtension for SystemModuleApiImpl<'a, K>
108{
109    fn read_bucket_uncosted(&self, bucket_id: &NodeId) -> Option<BucketSnapshot> {
110        let (is_fungible_bucket, resource_address) = if let Some(substate) =
111            self.api_ref().kernel_read_substate_uncosted(
112                &bucket_id,
113                TYPE_INFO_FIELD_PARTITION,
114                &TypeInfoField::TypeInfo.into(),
115            ) {
116            let type_info: TypeInfoSubstate = substate.as_typed().unwrap();
117            match type_info {
118                TypeInfoSubstate::Object(info)
119                    if info.blueprint_info.blueprint_id.package_address == RESOURCE_PACKAGE
120                        && (info.blueprint_info.blueprint_id.blueprint_name
121                            == FUNGIBLE_BUCKET_BLUEPRINT
122                            || info.blueprint_info.blueprint_id.blueprint_name
123                                == NON_FUNGIBLE_BUCKET_BLUEPRINT) =>
124                {
125                    let is_fungible = info
126                        .blueprint_info
127                        .blueprint_id
128                        .blueprint_name
129                        .eq(FUNGIBLE_BUCKET_BLUEPRINT);
130                    let parent = info.get_outer_object();
131                    let resource_address: ResourceAddress =
132                        ResourceAddress::new_or_panic(parent.as_bytes().try_into().unwrap());
133                    (is_fungible, resource_address)
134                }
135                _ => {
136                    return None;
137                }
138            }
139        } else {
140            return None;
141        };
142
143        if is_fungible_bucket {
144            let substate = self
145                .api_ref()
146                .kernel_read_substate_uncosted(
147                    bucket_id,
148                    MAIN_BASE_PARTITION,
149                    &FungibleBucketField::Liquid.into(),
150                )
151                .unwrap();
152            let liquid: FieldSubstate<LiquidFungibleResource> = substate.as_typed().unwrap();
153
154            Some(BucketSnapshot::Fungible {
155                resource_address,
156                liquid: liquid.into_payload().amount(),
157            })
158        } else {
159            let substate = self
160                .api_ref()
161                .kernel_read_substate_uncosted(
162                    bucket_id,
163                    MAIN_BASE_PARTITION,
164                    &NonFungibleBucketField::Liquid.into(),
165                )
166                .unwrap();
167            let liquid: FieldSubstate<LiquidNonFungibleResource> = substate.as_typed().unwrap();
168
169            Some(BucketSnapshot::NonFungible {
170                resource_address,
171                liquid: liquid.into_payload().ids().clone(),
172            })
173        }
174    }
175
176    fn read_proof_uncosted(&self, proof_id: &NodeId) -> Option<ProofSnapshot> {
177        let is_fungible = if let Some(substate) = self.api_ref().kernel_read_substate_uncosted(
178            &proof_id,
179            TYPE_INFO_FIELD_PARTITION,
180            &TypeInfoField::TypeInfo.into(),
181        ) {
182            let type_info: TypeInfoSubstate = substate.as_typed().unwrap();
183            match type_info {
184                TypeInfoSubstate::Object(ObjectInfo {
185                    blueprint_info: BlueprintInfo { blueprint_id, .. },
186                    ..
187                }) if blueprint_id.package_address == RESOURCE_PACKAGE
188                    && (blueprint_id.blueprint_name == NON_FUNGIBLE_PROOF_BLUEPRINT
189                        || blueprint_id.blueprint_name == FUNGIBLE_PROOF_BLUEPRINT) =>
190                {
191                    blueprint_id.blueprint_name.eq(FUNGIBLE_PROOF_BLUEPRINT)
192                }
193                _ => {
194                    return None;
195                }
196            }
197        } else {
198            return None;
199        };
200
201        if is_fungible {
202            let substate = self
203                .api_ref()
204                .kernel_read_substate_uncosted(
205                    proof_id,
206                    TYPE_INFO_FIELD_PARTITION,
207                    &TypeInfoField::TypeInfo.into(),
208                )
209                .unwrap();
210            let info: TypeInfoSubstate = substate.as_typed().unwrap();
211            let resource_address =
212                ResourceAddress::new_or_panic(info.outer_object().unwrap().into());
213
214            let substate = self
215                .api_ref()
216                .kernel_read_substate_uncosted(
217                    proof_id,
218                    MAIN_BASE_PARTITION,
219                    &FungibleProofField::ProofRefs.into(),
220                )
221                .unwrap();
222            let proof: FieldSubstate<FungibleProofSubstate> = substate.as_typed().unwrap();
223
224            Some(ProofSnapshot::Fungible {
225                resource_address,
226                total_locked: proof.into_payload().amount(),
227            })
228        } else {
229            let substate = self
230                .api_ref()
231                .kernel_read_substate_uncosted(
232                    proof_id,
233                    TYPE_INFO_FIELD_PARTITION,
234                    &TypeInfoField::TypeInfo.into(),
235                )
236                .unwrap();
237            let info: TypeInfoSubstate = substate.as_typed().unwrap();
238            let resource_address =
239                ResourceAddress::new_or_panic(info.outer_object().unwrap().into());
240
241            let substate = self
242                .api_ref()
243                .kernel_read_substate_uncosted(
244                    proof_id,
245                    MAIN_BASE_PARTITION,
246                    &NonFungibleProofField::ProofRefs.into(),
247                )
248                .unwrap();
249            let proof: FieldSubstate<NonFungibleProofSubstate> = substate.as_typed().unwrap();
250
251            Some(ProofSnapshot::NonFungible {
252                resource_address,
253                total_locked: proof.into_payload().non_fungible_local_ids().clone(),
254            })
255        }
256    }
257}
258
259#[derive(Clone, Debug, PartialEq, Eq, ScryptoSbor)]
260pub enum BucketSnapshot {
261    Fungible {
262        resource_address: ResourceAddress,
263        liquid: Decimal,
264    },
265    NonFungible {
266        resource_address: ResourceAddress,
267        liquid: IndexSet<NonFungibleLocalId>,
268    },
269}
270
271impl BucketSnapshot {
272    pub fn resource_address(&self) -> ResourceAddress {
273        match self {
274            BucketSnapshot::Fungible {
275                resource_address, ..
276            } => resource_address.clone(),
277            BucketSnapshot::NonFungible {
278                resource_address, ..
279            } => resource_address.clone(),
280        }
281    }
282    pub fn amount(&self) -> Decimal {
283        match self {
284            BucketSnapshot::Fungible { liquid, .. } => liquid.clone(),
285            BucketSnapshot::NonFungible { liquid, .. } => liquid.len().into(),
286        }
287    }
288}
289
290#[derive(Clone, Debug, PartialEq, Eq, ScryptoSbor)]
291pub enum ProofSnapshot {
292    Fungible {
293        resource_address: ResourceAddress,
294        total_locked: Decimal,
295    },
296    NonFungible {
297        resource_address: ResourceAddress,
298        total_locked: IndexSet<NonFungibleLocalId>,
299    },
300}
301
302impl ProofSnapshot {
303    pub fn resource_address(&self) -> ResourceAddress {
304        match self {
305            ProofSnapshot::Fungible {
306                resource_address, ..
307            } => resource_address.clone(),
308            ProofSnapshot::NonFungible {
309                resource_address, ..
310            } => resource_address.clone(),
311        }
312    }
313    pub fn amount(&self) -> Decimal {
314        match self {
315            ProofSnapshot::Fungible { total_locked, .. } => total_locked.clone(),
316            ProofSnapshot::NonFungible { total_locked, .. } => total_locked.len().into(),
317        }
318    }
319}
320
321#[derive(Debug, Clone, ScryptoSbor, PartialEq, Eq)]
322pub struct ResourceSummary {
323    pub buckets: IndexMap<NodeId, BucketSnapshot>,
324    pub proofs: IndexMap<NodeId, ProofSnapshot>,
325}
326
327// TODO: Clean up
328#[derive(Debug, Clone, ScryptoSbor, PartialEq, Eq)]
329pub enum TraceActor {
330    Method(NodeId),
331    NonMethod,
332}
333
334impl TraceActor {
335    pub fn from_actor(actor: &Actor) -> TraceActor {
336        match actor {
337            Actor::Method(MethodActor { node_id, .. }) => TraceActor::Method(node_id.clone()),
338            _ => TraceActor::NonMethod,
339        }
340    }
341}
342
343#[derive(Debug, Clone, ScryptoSbor, PartialEq, Eq)]
344pub struct ExecutionTrace {
345    pub origin: TraceOrigin,
346    pub kernel_call_depth: usize,
347    pub current_frame_actor: TraceActor,
348    pub current_frame_depth: usize,
349    pub instruction_index: usize,
350    pub input: ResourceSummary,
351    pub output: ResourceSummary,
352    pub children: Vec<ExecutionTrace>,
353}
354
355#[derive(Debug, Clone, Eq, PartialEq, ScryptoSbor)]
356pub struct ApplicationFnIdentifier {
357    pub blueprint_id: BlueprintId,
358    pub ident: String,
359}
360
361#[derive(Debug, Clone, Eq, PartialEq, ScryptoSbor)]
362pub enum TraceOrigin {
363    ScryptoFunction(ApplicationFnIdentifier),
364    ScryptoMethod(ApplicationFnIdentifier),
365    CreateNode,
366    DropNode,
367}
368
369impl ExecutionTrace {
370    pub fn worktop_changes(
371        &self,
372        worktop_changes_aggregator: &mut IndexMap<usize, Vec<WorktopChange>>,
373    ) {
374        if let TraceOrigin::ScryptoMethod(fn_identifier) = &self.origin {
375            if fn_identifier.blueprint_id == BlueprintId::new(&RESOURCE_PACKAGE, WORKTOP_BLUEPRINT)
376            {
377                if fn_identifier.ident == WORKTOP_PUT_IDENT {
378                    for (_, bucket_snapshot) in self.input.buckets.iter() {
379                        worktop_changes_aggregator
380                            .entry(self.instruction_index)
381                            .or_default()
382                            .push(WorktopChange::Put(bucket_snapshot.into()))
383                    }
384                } else if fn_identifier.ident == WORKTOP_TAKE_IDENT
385                    || fn_identifier.ident == WORKTOP_TAKE_ALL_IDENT
386                    || fn_identifier.ident == WORKTOP_TAKE_NON_FUNGIBLES_IDENT
387                    || fn_identifier.ident == WORKTOP_DRAIN_IDENT
388                {
389                    for (_, bucket_snapshot) in self.output.buckets.iter() {
390                        worktop_changes_aggregator
391                            .entry(self.instruction_index)
392                            .or_default()
393                            .push(WorktopChange::Take(bucket_snapshot.into()))
394                    }
395                }
396            }
397        }
398
399        // Aggregate the worktop changes for all children traces
400        for child in self.children.iter() {
401            child.worktop_changes(worktop_changes_aggregator)
402        }
403    }
404}
405
406impl ResourceSummary {
407    pub fn default() -> Self {
408        Self {
409            buckets: index_map_new(),
410            proofs: index_map_new(),
411        }
412    }
413
414    pub fn is_empty(&self) -> bool {
415        self.buckets.is_empty() && self.proofs.is_empty()
416    }
417
418    fn from_message(
419        api: &mut impl SystemModuleApiResourceSnapshotExtension,
420        message: &CallFrameMessage,
421    ) -> Self {
422        let mut buckets = index_map_new();
423        let mut proofs = index_map_new();
424        for node_id in &message.move_nodes {
425            if let Some(x) = api.read_bucket_uncosted(node_id) {
426                buckets.insert(*node_id, x);
427            }
428            if let Some(x) = api.read_proof_uncosted(node_id) {
429                proofs.insert(*node_id, x);
430            }
431        }
432        Self { buckets, proofs }
433    }
434
435    fn from_node_id(
436        api: &mut impl SystemModuleApiResourceSnapshotExtension,
437        node_id: &NodeId,
438    ) -> Self {
439        let mut buckets = index_map_new();
440        let mut proofs = index_map_new();
441        if let Some(x) = api.read_bucket_uncosted(node_id) {
442            buckets.insert(*node_id, x);
443        }
444        if let Some(x) = api.read_proof_uncosted(node_id) {
445            proofs.insert(*node_id, x);
446        }
447        Self { buckets, proofs }
448    }
449}
450
451impl InitSystemModule for ExecutionTraceModule {}
452impl ResolvableSystemModule for ExecutionTraceModule {
453    #[inline]
454    fn resolve_from_system(system: &mut impl HasModules) -> &mut Self {
455        &mut system.modules_mut().execution_trace
456    }
457}
458impl PrivilegedSystemModule for ExecutionTraceModule {}
459
460impl<ModuleApi: SystemModuleApiFor<Self> + SystemModuleApiResourceSnapshotExtension>
461    SystemModule<ModuleApi> for ExecutionTraceModule
462{
463    fn on_create_node(api: &mut ModuleApi, event: &CreateNodeEvent) -> Result<(), RuntimeError> {
464        if api.current_stack_id_uncosted() != 0 {
465            return Ok(());
466        }
467        match event {
468            CreateNodeEvent::Start(..) => {
469                api.module().handle_before_create_node();
470            }
471            CreateNodeEvent::IOAccess(..) => {}
472            CreateNodeEvent::End(node_id) => {
473                let current_depth = api.current_stack_depth_uncosted();
474                let resource_summary = ResourceSummary::from_node_id(api, node_id);
475                let system_state = api.system_state();
476                Self::resolve_from_system(system_state.system).handle_after_create_node(
477                    system_state.current_call_frame,
478                    current_depth,
479                    resource_summary,
480                );
481            }
482        }
483
484        Ok(())
485    }
486
487    fn on_drop_node(api: &mut ModuleApi, event: &DropNodeEvent) -> Result<(), RuntimeError> {
488        if api.current_stack_id_uncosted() != 0 {
489            return Ok(());
490        }
491
492        match event {
493            DropNodeEvent::Start(node_id) => {
494                let resource_summary = ResourceSummary::from_node_id(api, node_id);
495                api.module().handle_before_drop_node(resource_summary);
496            }
497            DropNodeEvent::End(..) => {
498                let current_depth = api.current_stack_depth_uncosted();
499                let system_state = api.system_state();
500                system_state
501                    .system
502                    .modules
503                    .execution_trace
504                    .handle_after_drop_node(system_state.current_call_frame, current_depth);
505            }
506            DropNodeEvent::IOAccess(_) => {}
507        }
508
509        Ok(())
510    }
511
512    fn before_invoke(
513        api: &mut ModuleApi,
514        invocation: &KernelInvocation<Actor>,
515    ) -> Result<(), RuntimeError> {
516        if api.current_stack_id_uncosted() != 0 {
517            return Ok(());
518        }
519
520        let message = CallFrameMessage::from_input(&invocation.args, &invocation.call_frame_data);
521        let resource_summary = ResourceSummary::from_message(api, &message);
522        let callee = &invocation.call_frame_data;
523        let args = &invocation.args;
524        let system_state = api.system_state();
525        system_state
526            .system
527            .modules
528            .execution_trace
529            .handle_before_invoke(
530                system_state.current_call_frame,
531                callee,
532                resource_summary,
533                args,
534            );
535        Ok(())
536    }
537
538    fn on_execution_finish(
539        api: &mut ModuleApi,
540        message: &CallFrameMessage,
541    ) -> Result<(), RuntimeError> {
542        if api.current_stack_id_uncosted() != 0 {
543            return Ok(());
544        }
545
546        let current_depth = api.current_stack_depth_uncosted();
547        let resource_summary = ResourceSummary::from_message(api, message);
548
549        let system_state = api.system_state();
550
551        let caller = TraceActor::from_actor(system_state.caller_call_frame);
552
553        system_state
554            .system
555            .modules
556            .execution_trace
557            .handle_on_execution_finish(
558                system_state.current_call_frame,
559                current_depth,
560                &caller,
561                resource_summary,
562            );
563
564        Ok(())
565    }
566}
567
568impl ExecutionTraceModule {
569    pub fn new(max_kernel_call_depth_traced: usize) -> ExecutionTraceModule {
570        Self {
571            max_kernel_call_depth_traced,
572            current_instruction_index: 0,
573            current_kernel_call_depth: 0,
574            traced_kernel_call_inputs_stack: vec![],
575            kernel_call_traces_stacks: index_map_new(),
576            vault_ops: Vec::new(),
577        }
578    }
579
580    fn handle_before_create_node(&mut self) {
581        // Important to always update the counter (even if we're over the depth limit).
582        self.current_kernel_call_depth += 1;
583        if self.current_kernel_call_depth - 1 > self.max_kernel_call_depth_traced {
584            return;
585        }
586
587        let instruction_index = self.instruction_index();
588        let traced_input = (
589            ResourceSummary::default(),
590            TraceOrigin::CreateNode,
591            instruction_index,
592        );
593        self.traced_kernel_call_inputs_stack.push(traced_input);
594    }
595
596    fn handle_after_create_node(
597        &mut self,
598        current_actor: &Actor,
599        current_depth: usize,
600        resource_summary: ResourceSummary,
601    ) {
602        // Important to always update the counter (even if we're over the depth limit).
603        self.current_kernel_call_depth -= 1;
604        if self.current_kernel_call_depth > self.max_kernel_call_depth_traced {
605            return;
606        }
607
608        let current_actor = TraceActor::from_actor(current_actor);
609        self.finalize_kernel_call_trace(resource_summary, current_actor, current_depth)
610    }
611
612    fn handle_before_drop_node(&mut self, resource_summary: ResourceSummary) {
613        // Important to always update the counter (even if we're over the depth limit).
614        self.current_kernel_call_depth += 1;
615        if self.current_kernel_call_depth - 1 > self.max_kernel_call_depth_traced {
616            return;
617        }
618
619        let instruction_index = self.instruction_index();
620        let traced_input = (resource_summary, TraceOrigin::DropNode, instruction_index);
621        self.traced_kernel_call_inputs_stack.push(traced_input);
622    }
623
624    fn handle_after_drop_node(&mut self, current_actor: &Actor, current_depth: usize) {
625        // Important to always update the counter (even if we're over the depth limit).
626        self.current_kernel_call_depth -= 1;
627        if self.current_kernel_call_depth > self.max_kernel_call_depth_traced {
628            return;
629        }
630
631        let traced_output = ResourceSummary::default();
632        let current_actor = TraceActor::from_actor(current_actor);
633        self.finalize_kernel_call_trace(traced_output, current_actor, current_depth)
634    }
635
636    fn handle_before_invoke(
637        &mut self,
638        current_actor: &Actor,
639        callee: &Actor,
640        resource_summary: ResourceSummary,
641        args: &IndexedScryptoValue,
642    ) {
643        // Important to always update the counter (even if we're over the depth limit).
644        self.current_kernel_call_depth += 1;
645        if self.current_kernel_call_depth - 1 > self.max_kernel_call_depth_traced {
646            return;
647        }
648
649        let origin = match &callee {
650            Actor::Method(actor @ MethodActor { ident, .. }) => {
651                TraceOrigin::ScryptoMethod(ApplicationFnIdentifier {
652                    blueprint_id: actor.get_blueprint_id(),
653                    ident: ident.clone(),
654                })
655            }
656            Actor::Function(FunctionActor {
657                blueprint_id,
658                ident,
659                ..
660            }) => TraceOrigin::ScryptoFunction(ApplicationFnIdentifier {
661                blueprint_id: blueprint_id.clone(),
662                ident: ident.clone(),
663            }),
664            Actor::BlueprintHook(..) | Actor::Root => {
665                return;
666            }
667        };
668        let instruction_index = self.instruction_index();
669        self.traced_kernel_call_inputs_stack.push((
670            resource_summary.clone(),
671            origin,
672            instruction_index,
673        ));
674
675        match &callee {
676            Actor::Method(actor @ MethodActor { node_id, ident, .. })
677                if VaultUtil::is_vault_blueprint(&actor.get_blueprint_id()) =>
678            {
679                match ident.as_str() {
680                    VAULT_PUT_IDENT => {
681                        self.handle_vault_put_input(&resource_summary, current_actor, node_id)
682                    }
683                    FUNGIBLE_VAULT_LOCK_FEE_IDENT => {
684                        self.handle_vault_lock_fee_input(current_actor, node_id, args)
685                    }
686                    VAULT_BURN_IDENT
687                    | VAULT_TAKE_IDENT
688                    | VAULT_TAKE_ADVANCED_IDENT
689                    | VAULT_RECALL_IDENT
690                    | VAULT_GET_AMOUNT_IDENT
691                    | VAULT_FREEZE_IDENT
692                    | VAULT_UNFREEZE_IDENT => { /* no-op */ }
693                    FUNGIBLE_VAULT_LOCK_FUNGIBLE_AMOUNT_IDENT
694                    | FUNGIBLE_VAULT_UNLOCK_FUNGIBLE_AMOUNT_IDENT
695                    | FUNGIBLE_VAULT_CREATE_PROOF_OF_AMOUNT_IDENT => { /* no-op */ }
696                    NON_FUNGIBLE_VAULT_TAKE_NON_FUNGIBLES_IDENT
697                    | NON_FUNGIBLE_VAULT_GET_NON_FUNGIBLE_LOCAL_IDS_IDENT
698                    | NON_FUNGIBLE_VAULT_CONTAINS_NON_FUNGIBLE_IDENT
699                    | NON_FUNGIBLE_VAULT_RECALL_NON_FUNGIBLES_IDENT
700                    | NON_FUNGIBLE_VAULT_CREATE_PROOF_OF_NON_FUNGIBLES_IDENT
701                    | NON_FUNGIBLE_VAULT_LOCK_NON_FUNGIBLES_IDENT
702                    | NON_FUNGIBLE_VAULT_UNLOCK_NON_FUNGIBLES_IDENT
703                    | NON_FUNGIBLE_VAULT_BURN_NON_FUNGIBLES_IDENT => { /* no-op */ }
704                    _ => panic!("Unhandled vault method"),
705                }
706            }
707            _ => { /* no-op */ }
708        }
709    }
710
711    fn handle_on_execution_finish(
712        &mut self,
713        current_actor: &Actor,
714        current_depth: usize,
715        caller: &TraceActor,
716        resource_summary: ResourceSummary,
717    ) {
718        // Important to always update the counter (even if we're over the depth limit).
719        self.current_kernel_call_depth -= 1;
720        if self.current_kernel_call_depth > self.max_kernel_call_depth_traced {
721            return;
722        }
723
724        match current_actor {
725            Actor::Method(actor @ MethodActor { node_id, ident, .. }) => {
726                if VaultUtil::is_vault_blueprint(&actor.get_blueprint_id()) {
727                    match ident.as_str() {
728                        VAULT_TAKE_IDENT | VAULT_TAKE_ADVANCED_IDENT | VAULT_RECALL_IDENT => {
729                            for (_, resource) in &resource_summary.buckets {
730                                let op = if ident == VAULT_TAKE_IDENT {
731                                    VaultOp::Take(resource.resource_address(), resource.amount())
732                                } else if ident == VAULT_TAKE_ADVANCED_IDENT {
733                                    VaultOp::TakeAdvanced(
734                                        resource.resource_address(),
735                                        resource.amount(),
736                                    )
737                                } else if ident == VAULT_RECALL_IDENT {
738                                    VaultOp::Recall(resource.resource_address(), resource.amount())
739                                } else {
740                                    panic!("Unhandled vault method")
741                                };
742                                self.vault_ops.push((
743                                    caller.clone(),
744                                    node_id.clone(),
745                                    op,
746                                    self.instruction_index(),
747                                ));
748                            }
749                        }
750                        VAULT_PUT_IDENT
751                        | VAULT_GET_AMOUNT_IDENT
752                        | VAULT_FREEZE_IDENT
753                        | VAULT_UNFREEZE_IDENT
754                        | VAULT_BURN_IDENT => { /* no-op */ }
755                        FUNGIBLE_VAULT_LOCK_FEE_IDENT
756                        | FUNGIBLE_VAULT_LOCK_FUNGIBLE_AMOUNT_IDENT
757                        | FUNGIBLE_VAULT_UNLOCK_FUNGIBLE_AMOUNT_IDENT
758                        | FUNGIBLE_VAULT_CREATE_PROOF_OF_AMOUNT_IDENT => { /* no-op */ }
759                        NON_FUNGIBLE_VAULT_TAKE_NON_FUNGIBLES_IDENT
760                        | NON_FUNGIBLE_VAULT_GET_NON_FUNGIBLE_LOCAL_IDS_IDENT
761                        | NON_FUNGIBLE_VAULT_CONTAINS_NON_FUNGIBLE_IDENT
762                        | NON_FUNGIBLE_VAULT_RECALL_NON_FUNGIBLES_IDENT
763                        | NON_FUNGIBLE_VAULT_CREATE_PROOF_OF_NON_FUNGIBLES_IDENT
764                        | NON_FUNGIBLE_VAULT_LOCK_NON_FUNGIBLES_IDENT
765                        | NON_FUNGIBLE_VAULT_UNLOCK_NON_FUNGIBLES_IDENT
766                        | NON_FUNGIBLE_VAULT_BURN_NON_FUNGIBLES_IDENT => { /* no-op */ }
767                        _ => panic!("Unhandled vault method"),
768                    }
769                }
770            }
771            Actor::Function(_) => {}
772            Actor::BlueprintHook(..) | Actor::Root => return,
773        }
774
775        let current_actor = TraceActor::from_actor(current_actor);
776        self.finalize_kernel_call_trace(resource_summary, current_actor, current_depth)
777    }
778
779    fn finalize_kernel_call_trace(
780        &mut self,
781        traced_output: ResourceSummary,
782        current_actor: TraceActor,
783        current_depth: usize,
784    ) {
785        let child_traces = self
786            .kernel_call_traces_stacks
787            .swap_remove(&(self.current_kernel_call_depth + 1))
788            .unwrap_or(vec![]);
789
790        let (traced_input, origin, instruction_index) = self
791            .traced_kernel_call_inputs_stack
792            .pop()
793            .expect("kernel call input stack underflow");
794
795        // Only include the trace if:
796        // * there's a non-empty traced input or output
797        // * OR there are any child traces: they need a parent regardless of whether it traces any inputs/outputs.
798        //   At some depth (up to the tracing limit) there must have been at least one traced input/output
799        //   so we need to include the full path up to the root.
800        if !traced_input.is_empty() || !traced_output.is_empty() || !child_traces.is_empty() {
801            let trace = ExecutionTrace {
802                origin,
803                kernel_call_depth: self.current_kernel_call_depth,
804                current_frame_actor: current_actor,
805                current_frame_depth: current_depth,
806                instruction_index,
807                input: traced_input,
808                output: traced_output,
809                children: child_traces,
810            };
811
812            let siblings = self
813                .kernel_call_traces_stacks
814                .entry(self.current_kernel_call_depth)
815                .or_insert(vec![]);
816            siblings.push(trace);
817        }
818    }
819
820    pub fn finalize(
821        mut self,
822        fee_payments: &IndexMap<NodeId, Decimal>,
823        is_success: bool,
824    ) -> TransactionExecutionTrace {
825        let mut execution_traces = Vec::new();
826        for (_, traces) in self.kernel_call_traces_stacks.drain(..) {
827            execution_traces.extend(traces);
828        }
829
830        let fee_locks = calculate_fee_locks(&self.vault_ops);
831        let resource_changes = calculate_resource_changes(self.vault_ops, fee_payments, is_success);
832
833        TransactionExecutionTrace {
834            execution_traces,
835            resource_changes,
836            fee_locks,
837        }
838    }
839
840    fn instruction_index(&self) -> usize {
841        self.current_instruction_index
842    }
843
844    fn handle_vault_put_input<'s>(
845        &mut self,
846        resource_summary: &ResourceSummary,
847        caller: &Actor,
848        vault_id: &NodeId,
849    ) {
850        let actor = TraceActor::from_actor(caller);
851        for (_, resource) in &resource_summary.buckets {
852            self.vault_ops.push((
853                actor.clone(),
854                vault_id.clone(),
855                VaultOp::Put(resource.resource_address(), resource.amount()),
856                self.instruction_index(),
857            ));
858        }
859    }
860
861    fn handle_vault_lock_fee_input<'s>(
862        &mut self,
863        caller: &Actor,
864        vault_id: &NodeId,
865        args: &IndexedScryptoValue,
866    ) {
867        let actor = TraceActor::from_actor(caller);
868        let FungibleVaultLockFeeInput { amount, contingent } = args.as_typed().unwrap();
869        self.vault_ops.push((
870            actor,
871            vault_id.clone(),
872            VaultOp::LockFee(amount, contingent),
873            self.instruction_index(),
874        ));
875    }
876}
877
878pub fn calculate_resource_changes(
879    mut vault_ops: Vec<(TraceActor, NodeId, VaultOp, usize)>,
880    fee_payments: &IndexMap<NodeId, Decimal>,
881    is_commit_success: bool,
882) -> IndexMap<usize, Vec<ResourceChange>> {
883    // Retain lock fee only if the transaction fails.
884    if !is_commit_success {
885        vault_ops.retain(|x| matches!(x.2, VaultOp::LockFee(..)));
886    }
887
888    // Calculate per instruction index, actor, vault resource changes.
889    let mut vault_changes =
890        index_map_new::<usize, IndexMap<NodeId, IndexMap<NodeId, (ResourceAddress, Decimal)>>>();
891    for (actor, vault_id, vault_op, instruction_index) in vault_ops {
892        if let TraceActor::Method(node_id) = actor {
893            match vault_op {
894                VaultOp::Put(resource_address, amount) => {
895                    let entry = &mut vault_changes
896                        .entry(instruction_index)
897                        .or_default()
898                        .entry(node_id)
899                        .or_default()
900                        .entry(vault_id)
901                        .or_insert((resource_address, Decimal::zero()))
902                        .1;
903                    *entry = entry.checked_add(amount).unwrap();
904                }
905                VaultOp::Take(resource_address, amount)
906                | VaultOp::TakeAdvanced(resource_address, amount)
907                | VaultOp::Recall(resource_address, amount) => {
908                    let entry = &mut vault_changes
909                        .entry(instruction_index)
910                        .or_default()
911                        .entry(node_id)
912                        .or_default()
913                        .entry(vault_id)
914                        .or_insert((resource_address, Decimal::zero()))
915                        .1;
916                    *entry = entry.checked_sub(amount).unwrap();
917                }
918                VaultOp::LockFee(..) => {
919                    let entry = &mut vault_changes
920                        .entry(instruction_index)
921                        .or_default()
922                        .entry(node_id)
923                        .or_default()
924                        .entry(vault_id)
925                        .or_insert((XRD, Decimal::zero()))
926                        .1;
927                    *entry = entry
928                        .checked_sub(fee_payments.get(&vault_id).cloned().unwrap_or_default())
929                        .unwrap();
930                }
931            }
932        }
933    }
934
935    // Convert into a vec for ease of consumption.
936    let mut resource_changes = index_map_new::<usize, Vec<ResourceChange>>();
937    for (instruction_index, instruction_resource_changes) in vault_changes {
938        for (node_id, map) in instruction_resource_changes {
939            for (vault_id, (resource_address, delta)) in map {
940                // Add a resource change log if non-zero
941                if !delta.is_zero() {
942                    resource_changes
943                        .entry(instruction_index)
944                        .or_default()
945                        .push(ResourceChange {
946                            resource_address,
947                            node_id,
948                            vault_id,
949                            amount: delta,
950                        });
951                }
952            }
953        }
954    }
955
956    resource_changes
957}
958
959pub fn calculate_fee_locks(vault_ops: &Vec<(TraceActor, NodeId, VaultOp, usize)>) -> FeeLocks {
960    let mut fee_locks = FeeLocks {
961        lock: Decimal::ZERO,
962        contingent_lock: Decimal::ZERO,
963    };
964    for (_, _, vault_op, _) in vault_ops {
965        if let VaultOp::LockFee(amount, is_contingent) = vault_op {
966            if !is_contingent {
967                fee_locks.lock = fee_locks.lock.checked_add(*amount).unwrap()
968            } else {
969                fee_locks.contingent_lock = fee_locks.contingent_lock.checked_add(*amount).unwrap()
970            }
971        };
972    }
973    fee_locks
974}