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