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#[derive(Debug, Clone)]
27pub struct ExecutionTraceModule {
28 max_kernel_call_depth_traced: usize,
30
31 current_instruction_index: usize,
33
34 current_kernel_call_depth: usize,
38
39 traced_kernel_call_inputs_stack: Vec<(ResourceSummary, TraceOrigin, usize)>,
41
42 kernel_call_traces_stacks: IndexMap<usize, Vec<ExecutionTrace>>,
44
45 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), 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#[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 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 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 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 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 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 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 => { }
693 FUNGIBLE_VAULT_LOCK_FUNGIBLE_AMOUNT_IDENT
694 | FUNGIBLE_VAULT_UNLOCK_FUNGIBLE_AMOUNT_IDENT
695 | FUNGIBLE_VAULT_CREATE_PROOF_OF_AMOUNT_IDENT => { }
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 => { }
704 _ => panic!("Unhandled vault method"),
705 }
706 }
707 _ => { }
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 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 => { }
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 => { }
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 => { }
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 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 if !is_commit_success {
885 vault_ops.retain(|x| matches!(x.2, VaultOp::LockFee(..)));
886 }
887
888 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 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 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}