1use super::*;
2use crate::blueprints::consensus_manager::EpochChangeEvent;
3use crate::errors::*;
4use crate::internal_prelude::*;
5use crate::kernel::kernel_callback_api::ExecutionReceipt;
6use crate::system::system_db_reader::SystemDatabaseReader;
7use crate::system::system_modules::costing::*;
8use crate::system::system_modules::execution_trace::*;
9use crate::system::system_substate_schemas::*;
10use crate::transaction::SystemStructure;
11use colored::*;
12use radix_engine_interface::blueprints::transaction_processor::InstructionOutput;
13use radix_transactions::prelude::*;
14use sbor::representations::*;
15
16#[derive(Clone, PartialEq, Eq)]
19pub struct TransactionReceipt {
20 pub costing_parameters: CostingParameters,
22 pub transaction_costing_parameters: TransactionCostingParametersReceiptV2,
24 pub fee_summary: TransactionFeeSummary,
26 pub fee_details: Option<TransactionFeeDetails>,
29 pub result: TransactionResult,
31 pub resources_usage: Option<ResourcesUsage>,
34 pub debug_information: Option<TransactionDebugInformation>,
37}
38
39pub type TransactionReceiptV1 = TransactionReceipt;
42
43#[cfg(all(feature = "std", feature = "flamegraph"))]
44impl TransactionReceipt {
45 pub fn generate_execution_breakdown_flamegraph_svg_bytes(
46 &self,
47 title: impl AsRef<str>,
48 network_definition: &NetworkDefinition,
49 ) -> Result<Vec<u8>, FlamegraphError> {
50 let title = title.as_ref();
51
52 let mut opts = inferno::flamegraph::Options::default();
54 "Execution Cost Units".clone_into(&mut opts.count_name);
55 opts.title = title.to_owned();
56
57 let Some(TransactionDebugInformation {
60 ref detailed_execution_cost_breakdown,
61 ..
62 }) = self.debug_information
63 else {
64 return Err(FlamegraphError::DetailedCostBreakdownNotAvailable);
65 };
66
67 let flamegraph_string = Self::transform_detailed_execution_breakdown_into_flamegraph_string(
68 detailed_execution_cost_breakdown,
69 network_definition,
70 );
71
72 let result = {
75 let tempfile = tempfile::NamedTempFile::new().map_err(FlamegraphError::IOError)?;
76 std::fs::write(&tempfile, flamegraph_string).map_err(FlamegraphError::IOError)?;
77
78 let mut result = std::io::Cursor::new(Vec::new());
79 inferno::flamegraph::from_files(&mut opts, &[tempfile.path().to_owned()], &mut result)
80 .map_err(|_| FlamegraphError::CreationError)?;
81
82 result.set_position(0);
83 result.into_inner()
84 };
85
86 Ok(result)
87 }
88
89 fn transform_detailed_execution_breakdown_into_flamegraph_string(
90 detailed_execution_cost_breakdown: &[DetailedExecutionCostBreakdownEntry],
91 network_definition: &NetworkDefinition,
92 ) -> String {
93 use crate::system::actor::*;
95
96 let address_bech32m_encoder = AddressBech32Encoder::new(&network_definition);
97
98 let mut lines = Vec::<String>::new();
99 let mut path_stack = vec![];
100 for (
101 index,
102 DetailedExecutionCostBreakdownEntry {
103 item: execution_item,
104 ..
105 },
106 ) in detailed_execution_cost_breakdown.iter().enumerate()
107 {
108 match execution_item {
110 ExecutionCostBreakdownItem::Invocation { actor, .. } => {
111 let actor_string = match actor {
112 Actor::Root => "root".to_owned(),
113 Actor::Method(MethodActor {
114 node_id,
115 ref ident,
116 ref object_info,
117 ..
118 }) => {
119 format!(
120 "Method <{}>::{}::{}",
121 address_bech32m_encoder
122 .encode(node_id.as_bytes())
123 .expect("Encoding of an address can't fail"),
124 object_info.blueprint_info.blueprint_id.blueprint_name,
125 ident
126 )
127 }
128 Actor::Function(FunctionActor {
129 ref blueprint_id,
130 ref ident,
131 ..
132 }) => {
133 format!(
134 "Function <{}>::{}::{}",
135 address_bech32m_encoder
136 .encode(blueprint_id.package_address.as_bytes())
137 .expect("Encoding of an address can't fail"),
138 blueprint_id.blueprint_name,
139 ident
140 )
141 }
142 Actor::BlueprintHook(BlueprintHookActor {
143 hook,
144 ref blueprint_id,
145 ..
146 }) => {
147 format!(
148 "Blueprint Hook <{}>::{}::{:?}",
149 address_bech32m_encoder
150 .encode(blueprint_id.package_address.as_bytes())
151 .expect("Encoding of an address can't fail"),
152 blueprint_id.blueprint_name,
153 hook
154 )
155 }
156 };
157 path_stack.push(format!("Invocation: {actor_string} ({index})"))
158 }
159 ExecutionCostBreakdownItem::InvocationComplete => {
160 path_stack.pop();
161 }
162 ExecutionCostBreakdownItem::Execution {
163 simple_name,
164 cost_units,
165 ..
166 } => {
167 lines.push(format!(
168 "{}{}({}) {}",
169 if path_stack.join(";").is_empty() {
170 "".to_owned()
171 } else {
172 format!("{};", path_stack.join(";"))
173 },
174 simple_name,
175 index,
176 cost_units
177 ));
178 }
179 }
180 }
181
182 lines.join("\n")
183 }
184}
185
186impl ExecutionReceipt for TransactionReceipt {
187 fn set_resource_usage(&mut self, resources_usage: ResourcesUsage) {
188 self.resources_usage = Some(resources_usage);
189 }
190}
191
192#[derive(Default, Debug, Clone, ScryptoSbor, PartialEq, Eq)]
193pub struct TransactionFeeSummary {
194 pub total_execution_cost_units_consumed: u32,
196 pub total_finalization_cost_units_consumed: u32,
198
199 pub total_execution_cost_in_xrd: Decimal,
201 pub total_finalization_cost_in_xrd: Decimal,
203 pub total_tipping_cost_in_xrd: Decimal,
205 pub total_storage_cost_in_xrd: Decimal,
207 pub total_royalty_cost_in_xrd: Decimal,
209}
210
211#[derive(Default, Debug, Clone, ScryptoSbor, PartialEq, Eq)]
212pub struct TransactionFeeDetails {
213 pub execution_cost_breakdown: BTreeMap<String, u32>,
215 pub finalization_cost_breakdown: BTreeMap<String, u32>,
217}
218
219#[derive(Debug, Clone, ScryptoSbor, PartialEq, Eq)]
221pub enum TransactionResult {
222 Commit(CommitResult),
223 Reject(RejectResult),
224 Abort(AbortResult),
225}
226
227#[derive(Debug, Clone, ScryptoSbor, PartialEq, Eq)]
228pub struct CommitResult {
229 pub state_updates: StateUpdates,
231 pub state_update_summary: StateUpdateSummary,
233 pub fee_source: FeeSource,
235 pub fee_destination: FeeDestination,
237 pub outcome: TransactionOutcome,
239 pub application_events: Vec<(EventTypeIdentifier, Vec<u8>)>,
241 pub application_logs: Vec<(Level, String)>,
243 pub system_structure: SystemStructure,
245 pub execution_trace: Option<TransactionExecutionTrace>,
248 pub performed_nullifications: Vec<Nullification>,
251}
252
253#[derive(Debug, Clone, Default, ScryptoSbor, PartialEq, Eq)]
254pub struct FeeSource {
255 pub paying_vaults: IndexMap<NodeId, Decimal>,
256}
257
258#[derive(Debug, Clone, Default, ScryptoSbor, PartialEq, Eq)]
259pub struct FeeDestination {
260 pub to_proposer: Decimal,
261 pub to_validator_set: Decimal,
262 pub to_burn: Decimal,
263 pub to_royalty_recipients: IndexMap<RoyaltyRecipient, Decimal>,
264}
265
266#[derive(Debug, Clone, ScryptoSbor, PartialEq, Eq)]
268pub enum TransactionOutcome {
269 Success(Vec<InstructionOutput>),
270 Failure(RuntimeError),
271}
272
273#[derive(Debug, Clone, ScryptoSbor, Default, PartialEq, Eq)]
274pub struct TransactionExecutionTrace {
275 pub execution_traces: Vec<ExecutionTrace>,
276 pub resource_changes: IndexMap<usize, Vec<ResourceChange>>,
277 pub fee_locks: FeeLocks,
278}
279
280#[derive(Debug, Clone, Eq, PartialEq, ScryptoSbor, Default)]
281pub struct FeeLocks {
282 pub lock: Decimal,
283 pub contingent_lock: Decimal,
284}
285
286#[derive(Debug, Copy, Clone, Eq, PartialEq, ScryptoSbor)]
287pub enum Nullification {
288 Intent {
289 expiry_epoch: Epoch,
290 intent_hash: IntentHash,
291 },
292}
293
294impl Nullification {
295 pub fn of_intent(
296 intent_hash_nullification: IntentHashNullification,
297 current_epoch: Epoch,
298 is_success: bool,
299 ) -> Option<Self> {
300 let (intent_hash, expiry_epoch) = match intent_hash_nullification {
301 IntentHashNullification::TransactionIntent {
302 intent_hash,
303 expiry_epoch,
304 } => (intent_hash.into(), expiry_epoch),
305 IntentHashNullification::SimulatedTransactionIntent { simulated } => {
306 let intent_hash = simulated.transaction_intent_hash();
307 let expiry_epoch = simulated.expiry_epoch(current_epoch);
308 (intent_hash.into(), expiry_epoch)
309 }
310 IntentHashNullification::Subintent {
311 intent_hash: subintent_hash,
312 expiry_epoch,
313 } => {
314 if !is_success {
317 return None;
318 }
319 (subintent_hash.into(), expiry_epoch)
320 }
321 IntentHashNullification::SimulatedSubintent { simulated } => {
322 if !is_success {
323 return None;
324 }
325 let subintent_hash = simulated.subintent_hash();
326 let expiry_epoch = simulated.expiry_epoch(current_epoch);
327 (subintent_hash.into(), expiry_epoch)
328 }
329 };
330 Some(Nullification::Intent {
331 expiry_epoch,
332 intent_hash,
333 })
334 }
335
336 pub fn transaction_tracker_keys(self) -> (Epoch, Hash) {
337 match self {
338 Nullification::Intent {
339 expiry_epoch,
340 intent_hash,
341 } => (expiry_epoch, intent_hash.into_hash()),
342 }
343 }
344}
345
346#[derive(Debug, Clone, ScryptoSbor, PartialEq, Eq)]
347pub struct RejectResult {
348 pub reason: RejectionReason,
349}
350
351#[derive(Debug, Clone, ScryptoSbor, PartialEq, Eq)]
352pub struct AbortResult {
353 pub reason: AbortReason,
354}
355
356#[derive(Debug, Clone, Display, PartialEq, Eq, Sbor)]
357pub enum AbortReason {
358 ConfiguredAbortTriggeredOnFeeLoanRepayment,
359}
360
361#[derive(Debug, Clone, Default, ScryptoSbor, PartialEq, Eq)]
362pub struct ResourcesUsage {
363 pub heap_allocations_sum: usize,
364 pub heap_peak_memory: usize,
365 pub cpu_cycles: u64,
366}
367
368#[derive(Clone, PartialEq, Eq)]
373pub struct TransactionDebugInformation {
374 pub detailed_execution_cost_breakdown: Vec<DetailedExecutionCostBreakdownEntry>,
377}
378
379impl TransactionExecutionTrace {
380 pub fn worktop_changes(&self) -> IndexMap<usize, Vec<WorktopChange>> {
381 let mut aggregator = index_map_new::<usize, Vec<WorktopChange>>();
382 for trace in &self.execution_traces {
383 trace.worktop_changes(&mut aggregator)
384 }
385 aggregator
386 }
387}
388
389impl TransactionResult {
390 pub fn is_commit_success(&self) -> bool {
391 match self {
392 TransactionResult::Commit(c) => matches!(c.outcome, TransactionOutcome::Success(_)),
393 _ => false,
394 }
395 }
396}
397
398impl CommitResult {
399 pub fn empty_with_outcome(outcome: TransactionOutcome) -> Self {
400 Self {
401 state_updates: Default::default(),
402 state_update_summary: Default::default(),
403 fee_source: Default::default(),
404 fee_destination: Default::default(),
405 outcome,
406 application_events: Default::default(),
407 application_logs: Default::default(),
408 system_structure: Default::default(),
409 execution_trace: Default::default(),
410 performed_nullifications: Default::default(),
411 }
412 }
413
414 pub fn next_epoch(&self) -> Option<EpochChangeEvent> {
415 for (ref event_type_id, ref event_data) in self.application_events.iter() {
417 let is_consensus_manager = match &event_type_id.0 {
418 Emitter::Method(node_id, ModuleId::Main)
419 if node_id.entity_type() == Some(EntityType::GlobalConsensusManager) =>
420 {
421 true
422 }
423 Emitter::Function(blueprint_id)
424 if blueprint_id.package_address.eq(&CONSENSUS_MANAGER_PACKAGE) =>
425 {
426 true
427 }
428 _ => false,
429 };
430
431 if is_consensus_manager {
432 if let Ok(epoch_change_event) = scrypto_decode::<EpochChangeEvent>(&event_data) {
433 return Some(epoch_change_event);
434 }
435 }
436 }
437 None
438 }
439
440 pub fn new_package_addresses(&self) -> &IndexSet<PackageAddress> {
441 &self.state_update_summary.new_packages
442 }
443
444 pub fn new_component_addresses(&self) -> &IndexSet<ComponentAddress> {
445 &self.state_update_summary.new_components
446 }
447
448 pub fn new_resource_addresses(&self) -> &IndexSet<ResourceAddress> {
449 &self.state_update_summary.new_resources
450 }
451
452 pub fn new_vault_addresses(&self) -> &IndexSet<InternalAddress> {
453 &self.state_update_summary.new_vaults
454 }
455
456 pub fn vault_balance_changes(&self) -> &IndexMap<NodeId, (ResourceAddress, BalanceChange)> {
457 &self.state_update_summary.vault_balance_changes
458 }
459
460 pub fn output<T: ScryptoDecode>(&self, nth: usize) -> T {
461 match &self.outcome {
462 TransactionOutcome::Success(o) => match o.get(nth) {
463 Some(InstructionOutput::CallReturn(value)) => {
464 scrypto_decode::<T>(value).expect("Output can't be converted")
465 }
466 _ => panic!("No output for [{}]", nth),
467 },
468 TransactionOutcome::Failure(_) => panic!("Transaction failed"),
469 }
470 }
471
472 pub fn state_updates(
473 &self,
474 ) -> BTreeMap<NodeId, BTreeMap<PartitionNumber, BTreeMap<SubstateKey, DatabaseUpdate>>> {
475 let mut updates = BTreeMap::<
476 NodeId,
477 BTreeMap<PartitionNumber, BTreeMap<SubstateKey, DatabaseUpdate>>,
478 >::new();
479 for (node_id, x) in &self.state_updates.by_node {
480 let NodeStateUpdates::Delta { by_partition } = x;
481 for (partition_num, y) in by_partition {
482 match y {
483 PartitionStateUpdates::Delta { by_substate } => {
484 for (substate_key, substate_update) in by_substate {
485 updates
486 .entry(node_id.clone())
487 .or_default()
488 .entry(partition_num.clone())
489 .or_default()
490 .insert(substate_key.clone(), substate_update.clone());
491 }
492 }
493 PartitionStateUpdates::Batch(BatchPartitionStateUpdate::Reset {
494 new_substate_values,
495 }) => {
496 for (substate_key, substate_value) in new_substate_values {
497 updates
498 .entry(node_id.clone())
499 .or_default()
500 .entry(partition_num.clone())
501 .or_default()
502 .insert(
503 substate_key.clone(),
504 DatabaseUpdate::Set(substate_value.clone()),
505 );
506 }
507 }
508 }
509 }
510 }
511 updates
512 }
513
514 pub fn state_updates_string(&self) -> String {
517 let mut buffer = String::new();
518 for (node_id, x) in &self.state_updates() {
519 buffer.push_str(&format!("\n{:?}, {:?}\n", node_id, node_id.entity_type()));
520 for (partition_num, y) in x {
521 buffer.push_str(&format!(" {:?}\n", partition_num));
522 for (substate_key, substate_update) in y {
523 buffer.push_str(&format!(
524 " {}\n",
525 match substate_key {
526 SubstateKey::Field(x) => format!("Field: {}", x),
527 SubstateKey::Map(x) =>
528 format!("Map: {:?}", scrypto_decode::<ScryptoValue>(&x).unwrap()),
529 SubstateKey::Sorted(x) => format!(
530 "Sorted: {:?}, {:?}",
531 x.0,
532 scrypto_decode::<ScryptoValue>(&x.1).unwrap()
533 ),
534 },
535 ));
536 buffer.push_str(&format!(
537 " {}\n",
538 match substate_update {
539 DatabaseUpdate::Set(x) =>
540 format!("Set: {:?}", scrypto_decode::<ScryptoValue>(&x).unwrap()),
541 DatabaseUpdate::Delete => format!("Delete"),
542 }
543 ));
544 }
545 }
546 }
547 buffer
548 }
549}
550
551impl TransactionOutcome {
552 pub fn is_success(&self) -> bool {
553 matches!(self, Self::Success(_))
554 }
555
556 pub fn expect_success(&self) -> &Vec<InstructionOutput> {
557 match self {
558 TransactionOutcome::Success(results) => results,
559 TransactionOutcome::Failure(error) => panic!("Outcome was a failure: {error:?}"),
560 }
561 }
562
563 pub fn expect_failure(&self) -> &RuntimeError {
564 match self {
565 TransactionOutcome::Success(_) => panic!("Outcome was an unexpected success"),
566 TransactionOutcome::Failure(error) => error,
567 }
568 }
569
570 pub fn success_or_else<E, F: Fn(&RuntimeError) -> E>(
571 &self,
572 f: F,
573 ) -> Result<&Vec<InstructionOutput>, E> {
574 match self {
575 TransactionOutcome::Success(results) => Ok(results),
576 TransactionOutcome::Failure(error) => Err(f(error)),
577 }
578 }
579}
580
581impl TransactionReceipt {
582 pub fn empty_with_commit(commit_result: CommitResult) -> Self {
584 Self {
585 costing_parameters: CostingParameters::babylon_genesis(),
586 transaction_costing_parameters: Default::default(),
587 fee_summary: Default::default(),
588 fee_details: Default::default(),
589 result: TransactionResult::Commit(commit_result),
590 resources_usage: Default::default(),
591 debug_information: Default::default(),
592 }
593 }
594
595 pub fn empty_commit_success() -> Self {
596 Self::empty_with_commit(CommitResult::empty_with_outcome(
597 TransactionOutcome::Success(vec![]),
598 ))
599 }
600
601 pub fn is_commit_success(&self) -> bool {
602 matches!(
603 self.result,
604 TransactionResult::Commit(CommitResult {
605 outcome: TransactionOutcome::Success(_),
606 ..
607 })
608 )
609 }
610
611 pub fn is_commit_failure(&self) -> bool {
612 matches!(
613 self.result,
614 TransactionResult::Commit(CommitResult {
615 outcome: TransactionOutcome::Failure(_),
616 ..
617 })
618 )
619 }
620
621 pub fn is_rejection(&self) -> bool {
622 matches!(self.result, TransactionResult::Reject(_))
623 }
624
625 pub fn expect_commit_ignore_outcome(&self) -> &CommitResult {
626 match &self.result {
627 TransactionResult::Commit(c) => c,
628 TransactionResult::Reject(e) => panic!("Transaction was rejected: {:?}", e),
629 TransactionResult::Abort(_) => panic!("Transaction was aborted"),
630 }
631 }
632
633 pub fn into_commit_ignore_outcome(self) -> CommitResult {
634 match self.result {
635 TransactionResult::Commit(c) => c,
636 TransactionResult::Reject(e) => panic!("Transaction was rejected: {:?}", e),
637 TransactionResult::Abort(_) => panic!("Transaction was aborted"),
638 }
639 }
640
641 pub fn expect_commit(&self, success: bool) -> &CommitResult {
642 let c = self.expect_commit_ignore_outcome();
643 if c.outcome.is_success() != success {
644 panic!(
645 "Expected {} but was {}: {:?}",
646 if success { "success" } else { "failure" },
647 if c.outcome.is_success() {
648 "success"
649 } else {
650 "failure"
651 },
652 c.outcome
653 )
654 }
655 c
656 }
657
658 pub fn expect_commit_success(&self) -> &CommitResult {
659 self.expect_commit(true)
660 }
661
662 pub fn expect_commit_failure(&self) -> &CommitResult {
663 self.expect_commit(false)
664 }
665
666 pub fn expect_commit_failure_containing_error(&self, error_needle: &str) {
667 let error_message = self
668 .expect_commit_failure()
669 .outcome
670 .expect_failure()
671 .to_string(NO_NETWORK);
672 assert!(
673 error_message.contains(error_needle),
674 "{error_needle:?} was not contained in RuntimeError"
675 );
676 }
677
678 pub fn expect_rejection(&self) -> &RejectionReason {
679 match &self.result {
680 TransactionResult::Commit(..) => panic!("Expected rejection but was commit"),
681 TransactionResult::Reject(ref r) => &r.reason,
682 TransactionResult::Abort(..) => panic!("Expected rejection but was abort"),
683 }
684 }
685
686 pub fn expect_rejection_containing_error(&self, error_needle: &str) {
687 let error_message = self.expect_rejection().to_string(NO_NETWORK);
688 assert!(
689 error_message.contains(error_needle),
690 "{error_needle:?} was not contained in RejectionReason"
691 );
692 }
693
694 pub fn expect_abortion(&self) -> &AbortReason {
695 match &self.result {
696 TransactionResult::Commit(..) => panic!("Expected abortion but was commit"),
697 TransactionResult::Reject(..) => panic!("Expected abortion but was reject"),
698 TransactionResult::Abort(ref r) => &r.reason,
699 }
700 }
701
702 pub fn expect_not_success(&self) {
703 match &self.result {
704 TransactionResult::Commit(c) => {
705 if c.outcome.is_success() {
706 panic!("Transaction succeeded unexpectedly")
707 }
708 }
709 TransactionResult::Reject(..) => {}
710 TransactionResult::Abort(..) => {}
711 }
712 }
713
714 pub fn expect_specific_rejection<F>(&self, f: F)
715 where
716 F: Fn(&RejectionReason) -> bool,
717 {
718 match &self.result {
719 TransactionResult::Commit(..) => panic!("Expected rejection but was committed"),
720 TransactionResult::Reject(result) => {
721 if !f(&result.reason) {
722 panic!(
723 "Expected specific rejection but was different error:\n{:?}",
724 self
725 );
726 }
727 }
728 TransactionResult::Abort(..) => panic!("Expected rejection but was abort"),
729 }
730 }
731
732 pub fn expect_failure(&self) -> &RuntimeError {
733 match &self.result {
734 TransactionResult::Commit(c) => match &c.outcome {
735 TransactionOutcome::Success(_) => panic!("Expected failure but was success"),
736 TransactionOutcome::Failure(error) => error,
737 },
738 TransactionResult::Reject(_) => panic!("Transaction was rejected"),
739 TransactionResult::Abort(..) => panic!("Transaction was aborted"),
740 }
741 }
742
743 pub fn expect_specific_failure<F>(&self, f: F)
744 where
745 F: Fn(&RuntimeError) -> bool,
746 {
747 if !f(self.expect_failure()) {
748 panic!(
749 "Expected specific failure but was different error:\n{:?}",
750 self
751 );
752 }
753 }
754
755 pub fn expect_auth_failure(&self) {
756 self.expect_specific_failure(|e| {
757 matches!(
758 e,
759 RuntimeError::SystemModuleError(SystemModuleError::AuthError(..))
760 )
761 })
762 }
763
764 pub fn expect_auth_assertion_failure(&self) {
765 self.expect_specific_failure(|e| {
766 matches!(
767 e,
768 RuntimeError::SystemError(SystemError::AssertAccessRuleFailed)
769 )
770 })
771 }
772
773 pub fn effective_execution_cost_unit_price(&self) -> Decimal {
774 self.costing_parameters
776 .execution_cost_unit_price
777 .checked_mul(
778 Decimal::ONE
779 .checked_add(self.transaction_costing_parameters.tip_proportion)
780 .unwrap(),
781 )
782 .unwrap()
783 }
784
785 pub fn effective_finalization_cost_unit_price(&self) -> Decimal {
786 let one_percent = Decimal::ONE_HUNDREDTH;
787
788 self.costing_parameters
790 .finalization_cost_unit_price
791 .checked_mul(
792 Decimal::ONE
793 .checked_add(
794 one_percent
795 .checked_mul(self.transaction_costing_parameters.tip_proportion)
796 .unwrap(),
797 )
798 .unwrap(),
799 )
800 .unwrap()
801 }
802}
803
804macro_rules! prefix {
805 ($i:expr, $list:expr) => {
806 if $i == $list.len() - 1 {
807 "└─"
808 } else {
809 "├─"
810 }
811 };
812}
813
814impl fmt::Debug for TransactionReceipt {
815 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
816 write!(
817 f,
818 "{}",
819 self.display(TransactionReceiptDisplayContext::default())
820 )
821 }
822}
823
824pub struct TransactionReceiptDisplayContext<'a> {
825 pub encoder: Option<&'a AddressBech32Encoder>,
826 pub system_database_reader: Option<SystemDatabaseReader<'a, dyn SubstateDatabase + 'a>>,
827 pub display_state_updates: bool,
828 pub use_ansi_colors: bool,
829 pub max_substate_length_to_display: usize,
830}
831
832impl<'a> Default for TransactionReceiptDisplayContext<'a> {
833 fn default() -> Self {
834 Self {
835 encoder: None,
836 system_database_reader: None,
837 display_state_updates: true,
838 use_ansi_colors: true,
839 max_substate_length_to_display: 1024,
840 }
841 }
842}
843
844impl<'a> TransactionReceiptDisplayContext<'a> {
845 pub fn display_context(&self) -> ScryptoValueDisplayContext<'a> {
846 ScryptoValueDisplayContext::with_optional_bech32(self.encoder)
847 }
848
849 pub fn address_display_context(&self) -> AddressDisplayContext<'a> {
850 AddressDisplayContext {
851 encoder: self.encoder,
852 }
853 }
854
855 pub fn max_substate_length_to_display(&self) -> usize {
856 self.max_substate_length_to_display
857 }
858
859 pub fn lookup_schema<T: AsRef<NodeId>>(
860 &self,
861 full_type_id: &FullyScopedTypeId<T>,
862 ) -> Option<(LocalTypeId, Rc<VersionedScryptoSchema>)> {
863 self.system_database_reader.as_ref().map(|system_reader| {
864 let schema = system_reader
865 .get_schema(full_type_id.0.as_ref(), &full_type_id.1)
866 .unwrap();
867
868 (full_type_id.2.clone(), schema)
869 })
870 }
871
872 fn format_first_top_level_title_with_detail<F: fmt::Write, D: fmt::Display>(
873 &self,
874 f: &mut F,
875 title: &str,
876 detail: D,
877 ) -> Result<(), fmt::Error> {
878 if self.use_ansi_colors {
879 write!(f, "{} {}", format!("{}:", title).bold().green(), detail)
880 } else {
881 write!(f, "{}: {}", title.to_uppercase(), detail)
882 }
883 }
884
885 fn format_top_level_title_with_detail<F: fmt::Write, D: fmt::Display>(
886 &self,
887 f: &mut F,
888 title: &str,
889 detail: D,
890 ) -> Result<(), fmt::Error> {
891 if self.use_ansi_colors {
892 write!(f, "\n{} {}", format!("{}:", title).bold().green(), detail)
893 } else {
894 write!(f, "\n\n{}: {}", title.to_uppercase(), detail)
895 }
896 }
897
898 fn display_title(&self, title: &str) -> MaybeAnsi {
899 if self.use_ansi_colors {
900 MaybeAnsi::Ansi(title.bold().green())
901 } else {
902 MaybeAnsi::Normal(title.to_string())
903 }
904 }
905
906 fn display_result(&self, result: &TransactionResult) -> MaybeAnsi {
907 let (string, format): (String, fn(String) -> ColoredString) = match result {
908 TransactionResult::Commit(c) => match &c.outcome {
909 TransactionOutcome::Success(_) => ("COMMITTED SUCCESS".to_string(), |x| x.green()),
910 TransactionOutcome::Failure(e) => (
911 format!("COMMITTED FAILURE: {}", e.display(self.display_context())),
912 |x| x.red(),
913 ),
914 },
915 TransactionResult::Reject(r) => (
916 format!("REJECTED: {}", r.reason.display(self.display_context())),
917 |x| x.red(),
918 ),
919 TransactionResult::Abort(a) => (format!("ABORTED: {}", a.reason), |x| x.bright_red()),
920 };
921 if self.use_ansi_colors {
922 MaybeAnsi::Ansi(format(string))
923 } else {
924 MaybeAnsi::Normal(string)
925 }
926 }
927
928 fn display_log(&self, level: &Level, message: &str) -> (MaybeAnsi, MaybeAnsi) {
929 let (level, format): (_, fn(&str) -> ColoredString) = match level {
930 Level::Error => ("ERROR", |x| x.red()),
931 Level::Warn => ("WARN", |x| x.yellow()),
932 Level::Info => ("INFO", |x| x.green()),
933 Level::Debug => ("DEBUG", |x| x.cyan()),
934 Level::Trace => ("TRACE", |x| x.normal()),
935 };
936
937 if self.use_ansi_colors {
938 (
939 MaybeAnsi::Ansi(format(level)),
940 MaybeAnsi::Ansi(format(message)),
941 )
942 } else {
943 (
944 MaybeAnsi::Normal(level.to_string()),
945 MaybeAnsi::Normal(message.to_string()),
946 )
947 }
948 }
949}
950
951enum MaybeAnsi {
952 Ansi(ColoredString),
953 Normal(String),
954}
955
956impl fmt::Display for MaybeAnsi {
957 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
958 match self {
959 MaybeAnsi::Ansi(value) => write!(f, "{}", value),
960 MaybeAnsi::Normal(value) => write!(f, "{}", value),
961 }
962 }
963}
964
965impl<'a> From<&'a AddressBech32Encoder> for TransactionReceiptDisplayContext<'a> {
966 fn from(encoder: &'a AddressBech32Encoder) -> Self {
967 Self {
968 encoder: Some(encoder),
969 ..Default::default()
970 }
971 }
972}
973
974impl<'a> From<Option<&'a AddressBech32Encoder>> for TransactionReceiptDisplayContext<'a> {
975 fn from(encoder: Option<&'a AddressBech32Encoder>) -> Self {
976 Self {
977 encoder,
978 ..Default::default()
979 }
980 }
981}
982
983pub struct TransactionReceiptDisplayContextBuilder<'a>(TransactionReceiptDisplayContext<'a>);
984
985impl<'a> TransactionReceiptDisplayContextBuilder<'a> {
986 pub fn new() -> Self {
987 Self(Default::default())
988 }
989
990 pub fn encoder(mut self, encoder: &'a AddressBech32Encoder) -> Self {
991 self.0.encoder = Some(encoder);
992 self
993 }
994
995 pub fn schema_lookup_from_db(mut self, db: &'a dyn SubstateDatabase) -> Self {
996 self.0.system_database_reader = Some(SystemDatabaseReader::new(db));
997 self
998 }
999
1000 pub fn display_state_updates(mut self, setting: bool) -> Self {
1001 self.0.display_state_updates = setting;
1002 self
1003 }
1004
1005 pub fn use_ansi_colors(mut self, setting: bool) -> Self {
1006 self.0.use_ansi_colors = setting;
1007 self
1008 }
1009
1010 pub fn set_max_substate_length_to_display(mut self, setting: usize) -> Self {
1011 self.0.max_substate_length_to_display = setting;
1012 self
1013 }
1014
1015 pub fn build(self) -> TransactionReceiptDisplayContext<'a> {
1016 self.0
1017 }
1018}
1019
1020impl<'a> ContextualDisplay<TransactionReceiptDisplayContext<'a>> for TransactionReceipt {
1021 type Error = fmt::Error;
1022
1023 fn contextual_format<F: fmt::Write>(
1024 &self,
1025 f: &mut F,
1026 context: &TransactionReceiptDisplayContext<'a>,
1027 ) -> Result<(), Self::Error> {
1028 let result = &self.result;
1029 let scrypto_value_display_context = context.display_context();
1030 let address_display_context = context.address_display_context();
1031
1032 context.format_first_top_level_title_with_detail(
1033 f,
1034 "Transaction Status",
1035 context.display_result(result),
1036 )?;
1037
1038 context.format_top_level_title_with_detail(
1039 f,
1040 "Transaction Cost",
1041 format!("{} XRD", self.fee_summary.total_cost()),
1042 )?;
1043 write!(
1044 f,
1045 "\n├─ {} {} XRD, {} execution cost units",
1046 context.display_title("Network execution:"),
1047 self.fee_summary.total_execution_cost_in_xrd,
1048 self.fee_summary.total_execution_cost_units_consumed,
1049 )?;
1050 write!(
1051 f,
1052 "\n├─ {} {} XRD, {} finalization cost units",
1053 context.display_title("Network finalization:"),
1054 self.fee_summary.total_finalization_cost_in_xrd,
1055 self.fee_summary.total_finalization_cost_units_consumed,
1056 )?;
1057 write!(
1058 f,
1059 "\n├─ {} {} XRD",
1060 context.display_title("Tip:"),
1061 self.fee_summary.total_tipping_cost_in_xrd
1062 )?;
1063 write!(
1064 f,
1065 "\n├─ {} {} XRD",
1066 context.display_title("Network Storage:"),
1067 self.fee_summary.total_storage_cost_in_xrd
1068 )?;
1069 write!(
1070 f,
1071 "\n└─ {} {} XRD",
1072 context.display_title("Royalties:"),
1073 self.fee_summary.total_royalty_cost_in_xrd
1074 )?;
1075
1076 if let TransactionResult::Commit(c) = &result {
1077 context.format_top_level_title_with_detail(f, "Logs", c.application_logs.len())?;
1078 for (i, (level, msg)) in c.application_logs.iter().enumerate() {
1079 let (level, msg) = context.display_log(level, msg);
1080 write!(
1081 f,
1082 "\n{} [{:5}] {}",
1083 prefix!(i, c.application_logs),
1084 level,
1085 msg
1086 )?;
1087 }
1088
1089 context.format_top_level_title_with_detail(f, "Events", c.application_events.len())?;
1090 for (i, (event_type_identifier, event_data)) in c.application_events.iter().enumerate()
1091 {
1092 display_event(
1093 f,
1094 prefix!(i, c.application_events),
1095 event_type_identifier,
1096 &c.system_structure,
1097 event_data,
1098 context,
1099 )?;
1100 }
1101
1102 if context.display_state_updates {
1103 (&c.state_updates, &c.system_structure).contextual_format(f, context)?;
1104 }
1105
1106 if let TransactionOutcome::Success(outputs) = &c.outcome {
1107 context.format_top_level_title_with_detail(f, "Outputs", outputs.len())?;
1108 for (i, output) in outputs.iter().enumerate() {
1109 write!(
1110 f,
1111 "\n{} {}",
1112 prefix!(i, outputs),
1113 match output {
1114 InstructionOutput::CallReturn(x) => IndexedScryptoValue::from_slice(&x)
1115 .expect("Impossible case! Instruction output can't be decoded")
1116 .to_string(ValueDisplayParameters::Schemaless {
1117 display_mode: DisplayMode::RustLike(RustLikeOptions::full()),
1118 print_mode: PrintMode::MultiLine {
1119 indent_size: 2,
1120 base_indent: 3,
1121 first_line_indent: 0
1122 },
1123 custom_context: scrypto_value_display_context,
1124 depth_limit: SCRYPTO_SBOR_V1_MAX_DEPTH
1125 }),
1126 InstructionOutput::None => "None".to_string(),
1127 }
1128 )?;
1129 }
1130 }
1131
1132 let balance_changes = c.vault_balance_changes();
1133 context.format_top_level_title_with_detail(
1134 f,
1135 "Balance Changes",
1136 balance_changes.len(),
1137 )?;
1138 for (i, (vault_id, (resource, delta))) in balance_changes.iter().enumerate() {
1139 write!(
1140 f,
1141 "\n{} Vault: {}\n ResAddr: {}\n Change: {}",
1144 prefix!(i, balance_changes),
1145 vault_id.display(address_display_context),
1146 resource.display(address_display_context),
1147 match delta {
1148 BalanceChange::Fungible(d) => format!("{}", d),
1149 BalanceChange::NonFungible { added, removed } => {
1150 format!("+{:?}, -{:?}", added, removed)
1151 }
1152 }
1153 )?;
1154 }
1155
1156 context.format_top_level_title_with_detail(
1157 f,
1158 "New Entities",
1159 c.new_package_addresses().len()
1160 + c.new_component_addresses().len()
1161 + c.new_resource_addresses().len(),
1162 )?;
1163 for (i, package_address) in c.new_package_addresses().iter().enumerate() {
1164 write!(
1165 f,
1166 "\n{} Package: {}",
1167 prefix!(i, c.new_package_addresses()),
1168 package_address.display(address_display_context)
1169 )?;
1170 }
1171 for (i, component_address) in c.new_component_addresses().iter().enumerate() {
1172 write!(
1173 f,
1174 "\n{} Component: {}",
1175 prefix!(i, c.new_component_addresses()),
1176 component_address.display(address_display_context)
1177 )?;
1178 }
1179 for (i, resource_address) in c.new_resource_addresses().iter().enumerate() {
1180 write!(
1181 f,
1182 "\n{} Resource: {}",
1183 prefix!(i, c.new_resource_addresses()),
1184 resource_address.display(address_display_context)
1185 )?;
1186 }
1187 }
1188
1189 Ok(())
1190 }
1191}
1192
1193impl<'a, 'b> ContextualDisplay<TransactionReceiptDisplayContext<'a>>
1194 for (&'b StateUpdates, &'b SystemStructure)
1195{
1196 type Error = fmt::Error;
1197
1198 fn contextual_format<F: fmt::Write>(
1199 &self,
1200 f: &mut F,
1201 context: &TransactionReceiptDisplayContext<'a>,
1202 ) -> Result<(), Self::Error> {
1203 let state_updates = self.0;
1204 let system_structure = self.1;
1205 context.format_top_level_title_with_detail(
1206 f,
1207 "State Updates",
1208 format!(
1209 "{} {}",
1210 state_updates.by_node.len(),
1211 if state_updates.by_node.len() == 1 {
1212 "entity"
1213 } else {
1214 "entities"
1215 },
1216 ),
1217 )?;
1218 for (i, (node_id, node_updates)) in state_updates.by_node.iter().enumerate() {
1219 let by_partition = match node_updates {
1220 NodeStateUpdates::Delta { by_partition } => by_partition,
1221 };
1222 write!(
1223 f,
1224 "\n{} {} across {} partitions",
1225 prefix!(i, state_updates.by_node),
1226 node_id.display(context.address_display_context()),
1227 by_partition.len(),
1228 )?;
1229
1230 for (j, (partition_number, partition_updates)) in by_partition.iter().enumerate() {
1231 match partition_updates {
1235 PartitionStateUpdates::Delta { by_substate } => {
1236 write!(
1237 f,
1238 "\n {} Partition({}): {} {}",
1239 prefix!(j, by_partition),
1240 partition_number.0,
1241 by_substate.len(),
1242 if by_substate.len() == 1 {
1243 "change"
1244 } else {
1245 "changes"
1246 },
1247 )?;
1248 for (k, (substate_key, update)) in by_substate.iter().enumerate() {
1249 display_substate_change(
1250 f,
1251 prefix!(k, by_substate),
1252 system_structure,
1253 context,
1254 node_id,
1255 partition_number,
1256 substate_key,
1257 update.as_ref(),
1258 )?;
1259 }
1260 }
1261 PartitionStateUpdates::Batch(BatchPartitionStateUpdate::Reset {
1262 new_substate_values,
1263 }) => {
1264 write!(
1265 f,
1266 "\n {} Partition({}): RESET ({} new values)",
1267 prefix!(j, by_partition),
1268 partition_number.0,
1269 new_substate_values.len()
1270 )?;
1271 for (k, (substate_key, value)) in new_substate_values.iter().enumerate() {
1272 display_substate_change(
1273 f,
1274 prefix!(k, new_substate_values),
1275 system_structure,
1276 context,
1277 node_id,
1278 partition_number,
1279 substate_key,
1280 DatabaseUpdateRef::Set(value),
1281 )?;
1282 }
1283 }
1284 }
1285 }
1286 }
1287 Ok(())
1288 }
1289}
1290
1291fn display_substate_change<'a, F: fmt::Write>(
1292 f: &mut F,
1293 prefix: &str,
1294 system_structure: &SystemStructure,
1295 receipt_context: &TransactionReceiptDisplayContext<'a>,
1296 node_id: &NodeId,
1297 partition_number: &PartitionNumber,
1298 substate_key: &SubstateKey,
1299 change: DatabaseUpdateRef,
1300) -> Result<(), fmt::Error> {
1301 let substate_structure = system_structure
1302 .substate_system_structures
1303 .get(node_id)
1304 .unwrap()
1305 .get(partition_number)
1306 .unwrap()
1307 .get(substate_key)
1308 .unwrap();
1309 match change {
1310 DatabaseUpdateRef::Set(substate_value) => {
1311 write!(f, "\n {prefix} Set: ")?;
1312 format_receipt_substate_key(f, substate_structure, receipt_context, substate_key)?;
1313 write!(f, "\n Value: ")?;
1314 format_receipt_substate_value(f, substate_structure, receipt_context, substate_value)?;
1315 }
1316 DatabaseUpdateRef::Delete => {
1317 write!(f, "\n {prefix} Delete: ")?;
1318 format_receipt_substate_key(f, substate_structure, receipt_context, substate_key)?;
1319 }
1320 }
1321 Ok(())
1322}
1323
1324fn format_receipt_substate_key<'a, F: fmt::Write>(
1325 f: &mut F,
1326 substate_structure: &SubstateSystemStructure,
1327 receipt_context: &TransactionReceiptDisplayContext<'a>,
1328 substate_key: &SubstateKey,
1329) -> Result<(), fmt::Error> {
1330 let print_mode = PrintMode::SingleLine;
1331 match substate_structure {
1332 SubstateSystemStructure::SystemField(structure) => {
1333 write!(f, "{:?}", structure.field_kind)
1334 }
1335 SubstateSystemStructure::SystemSchema => {
1336 let key_contents = substate_key.for_map().unwrap();
1337 let hash: SchemaHash = scrypto_decode(&*key_contents).unwrap();
1338 write!(f, "SchemaHash({})", hash.0)
1339 }
1340 SubstateSystemStructure::KeyValueStoreEntry(structure) => {
1341 let value = scrypto_decode(substate_key.for_map().unwrap()).unwrap();
1342 format_scrypto_value_with_full_type_id(
1343 f,
1344 print_mode,
1345 value,
1346 receipt_context,
1347 &structure.key_full_type_id,
1348 )
1349 }
1350 SubstateSystemStructure::ObjectField(_) => {
1351 let key_contents = substate_key.for_field().unwrap();
1352 write!(f, "Field({})", key_contents)
1353 }
1354 SubstateSystemStructure::ObjectKeyValuePartitionEntry(structure) => {
1355 let value = scrypto_decode(substate_key.for_map().unwrap()).unwrap();
1356 let full_type_id = extract_object_type_id(&structure.key_schema);
1357 format_scrypto_value_with_full_type_id(
1358 f,
1359 print_mode,
1360 value,
1361 receipt_context,
1362 &full_type_id,
1363 )
1364 }
1365 SubstateSystemStructure::ObjectIndexPartitionEntry(structure) => {
1366 let value = scrypto_decode(substate_key.for_map().unwrap()).unwrap();
1367 let full_type_id = extract_object_type_id(&structure.key_schema);
1368 format_scrypto_value_with_full_type_id(
1369 f,
1370 print_mode,
1371 value,
1372 receipt_context,
1373 &full_type_id,
1374 )
1375 }
1376 SubstateSystemStructure::ObjectSortedIndexPartitionEntry(structure) => {
1377 let (sort_bytes, key_contents) = substate_key.for_sorted().unwrap();
1378 let value = scrypto_decode(key_contents).unwrap();
1379 let full_type_id = extract_object_type_id(&structure.key_schema);
1380 write!(f, "SortKey({}, ", u16::from_be_bytes(sort_bytes.clone()))?;
1381 format_scrypto_value_with_full_type_id(
1382 f,
1383 print_mode,
1384 value,
1385 receipt_context,
1386 &full_type_id,
1387 )?;
1388 write!(f, ")")
1389 }
1390 }
1391}
1392
1393pub fn format_receipt_substate_value<'a, F: fmt::Write>(
1394 f: &mut F,
1395 substate_structure: &SubstateSystemStructure,
1396 receipt_context: &TransactionReceiptDisplayContext<'a>,
1397 substate_value: &[u8],
1398) -> Result<(), fmt::Error> {
1399 let print_mode = PrintMode::MultiLine {
1400 indent_size: 2,
1401 base_indent: 7,
1402 first_line_indent: 0,
1403 };
1404 if substate_value.len() > receipt_context.max_substate_length_to_display() {
1405 write!(
1406 f,
1407 "(Hidden as longer than {} bytes. Hash: {})",
1408 receipt_context.max_substate_length_to_display(),
1409 hash(substate_value)
1410 )
1411 } else {
1412 let (payload, full_type_id) = match substate_structure {
1413 SubstateSystemStructure::SystemField(structure) => {
1414 let single_type_schema = resolve_system_field_schema(structure.field_kind);
1415 let raw_value = scrypto_decode(substate_value).unwrap();
1416 return format_scrypto_value_with_schema(
1417 f,
1418 print_mode,
1419 raw_value,
1420 receipt_context,
1421 &single_type_schema.schema,
1422 single_type_schema.type_id,
1423 );
1424 }
1425 SubstateSystemStructure::SystemSchema => {
1426 let single_type_schema = resolve_system_schema_schema();
1427 let raw_value = scrypto_decode(substate_value).unwrap();
1428 return format_scrypto_value_with_schema(
1429 f,
1430 print_mode,
1431 raw_value,
1432 receipt_context,
1433 &single_type_schema.schema,
1434 single_type_schema.type_id,
1435 );
1436 }
1437 SubstateSystemStructure::KeyValueStoreEntry(structure) => {
1438 let payload =
1439 scrypto_decode::<KeyValueEntrySubstate<ScryptoRawValue>>(substate_value)
1440 .unwrap();
1441 (payload.into_value(), structure.value_full_type_id.clone())
1442 }
1443 SubstateSystemStructure::ObjectField(structure) => {
1444 let payload =
1445 scrypto_decode::<FieldSubstate<ScryptoRawValue>>(substate_value).unwrap();
1446 write_lock_status(f, payload.lock_status())?;
1447 (
1448 Some(payload.into_payload()),
1449 extract_object_type_id(&structure.value_schema),
1450 )
1451 }
1452 SubstateSystemStructure::ObjectKeyValuePartitionEntry(structure) => {
1453 let payload =
1454 scrypto_decode::<KeyValueEntrySubstate<ScryptoRawValue>>(substate_value)
1455 .unwrap();
1456 write_lock_status(f, payload.lock_status())?;
1457 (
1458 payload.into_value(),
1459 extract_object_type_id(&structure.value_schema),
1460 )
1461 }
1462 SubstateSystemStructure::ObjectIndexPartitionEntry(structure) => {
1463 let payload =
1464 scrypto_decode::<IndexEntrySubstate<ScryptoRawValue>>(substate_value).unwrap();
1465 (
1466 Some(payload.into_value()),
1467 extract_object_type_id(&structure.value_schema),
1468 )
1469 }
1470 SubstateSystemStructure::ObjectSortedIndexPartitionEntry(structure) => {
1471 let payload =
1472 scrypto_decode::<SortedIndexEntrySubstate<ScryptoRawValue>>(substate_value)
1473 .unwrap();
1474 (
1475 Some(payload.into_value()),
1476 extract_object_type_id(&structure.value_schema),
1477 )
1478 }
1479 };
1480 match payload {
1481 Some(payload) => format_scrypto_value_with_full_type_id(
1482 f,
1483 print_mode,
1484 payload,
1485 receipt_context,
1486 &full_type_id,
1487 ),
1488 None => write!(f, "EMPTY"),
1489 }
1490 }
1491}
1492
1493fn write_lock_status<F: fmt::Write>(f: &mut F, lock_status: LockStatus) -> Result<(), fmt::Error> {
1494 match lock_status {
1495 LockStatus::Unlocked => write!(f, "UNLOCKED "),
1496 LockStatus::Locked => write!(f, "LOCKED "),
1497 }
1498}
1499
1500fn extract_object_type_id(structure: &ObjectSubstateTypeReference) -> FullyScopedTypeId<NodeId> {
1501 match structure {
1502 ObjectSubstateTypeReference::Package(r) => r.full_type_id.clone().into_general(),
1503 ObjectSubstateTypeReference::ObjectInstance(r) => r.resolved_full_type_id.clone(),
1504 }
1505}
1506
1507fn display_event<'a, F: fmt::Write>(
1508 f: &mut F,
1509 prefix: &str,
1510 event_type_identifier: &EventTypeIdentifier,
1511 system_structure: &SystemStructure,
1512 event_data: &[u8],
1513 receipt_context: &TransactionReceiptDisplayContext<'a>,
1514) -> Result<(), fmt::Error> {
1515 let event_system_structure = system_structure
1516 .event_system_structures
1517 .get(event_type_identifier)
1518 .expect("Expected event to appear in the system structure");
1519
1520 let full_type_id = event_system_structure.package_type_reference.full_type_id;
1521 let schema_lookup = receipt_context.lookup_schema(&full_type_id);
1522 let emitter = &event_type_identifier.0;
1523 let print_mode = PrintMode::MultiLine {
1524 indent_size: 2,
1525 base_indent: 3,
1526 first_line_indent: 0,
1527 };
1528 let raw_value = scrypto_decode::<ScryptoRawValue>(event_data).unwrap();
1529 if let Some(_) = schema_lookup {
1530 write!(
1531 f,
1532 "\n{} Emitter: {}\n Event: ",
1533 prefix,
1534 emitter.display(receipt_context.address_display_context()),
1535 )?;
1536 format_scrypto_value_with_full_type_id(
1537 f,
1538 print_mode,
1539 raw_value,
1540 receipt_context,
1541 &full_type_id,
1542 )?;
1543 } else {
1544 write!(
1545 f,
1546 "\n{} Emitter: {}\n Name: {:?}\n Data: ",
1547 prefix,
1548 emitter.display(receipt_context.address_display_context()),
1549 event_type_identifier.1,
1550 )?;
1551 format_scrypto_value_with_full_type_id(
1552 f,
1553 print_mode,
1554 raw_value,
1555 receipt_context,
1556 &full_type_id,
1557 )?;
1558 }
1559 Ok(())
1560}
1561
1562fn format_scrypto_value_with_full_type_id<'a, F: fmt::Write, T: AsRef<NodeId>>(
1563 f: &mut F,
1564 print_mode: PrintMode,
1565 raw_value: ScryptoRawValue<'_>,
1566 receipt_context: &TransactionReceiptDisplayContext<'a>,
1567 full_type_id: &FullyScopedTypeId<T>,
1568) -> Result<(), fmt::Error> {
1569 let schema_lookup = receipt_context.lookup_schema(full_type_id);
1570 match schema_lookup {
1571 Some((local_type_id, schema)) => format_scrypto_value_with_schema(
1572 f,
1573 print_mode,
1574 raw_value,
1575 receipt_context,
1576 &schema,
1577 local_type_id,
1578 ),
1579 None => {
1580 let display_parameters: ValueDisplayParameters<'_, '_, ScryptoCustomExtension> =
1581 ValueDisplayParameters::Schemaless {
1582 display_mode: DisplayMode::RustLike(RustLikeOptions::full()),
1583 print_mode,
1584 custom_context: receipt_context.display_context(),
1585 depth_limit: SCRYPTO_SBOR_V1_MAX_DEPTH,
1586 };
1587 write!(f, "{}", raw_value.display(display_parameters))
1588 }
1589 }
1590}
1591
1592fn format_scrypto_value_with_schema<'a, F: fmt::Write>(
1593 f: &mut F,
1594 print_mode: PrintMode,
1595 raw_value: ScryptoRawValue<'_>,
1596 receipt_context: &TransactionReceiptDisplayContext<'a>,
1597 schema: &VersionedScryptoSchema,
1598 local_type_id: LocalTypeId,
1599) -> Result<(), fmt::Error> {
1600 let display_parameters = ValueDisplayParameters::Annotated {
1601 display_mode: DisplayMode::RustLike(RustLikeOptions::full()),
1602 print_mode,
1603 custom_context: receipt_context.display_context(),
1604 schema: schema.v1(),
1605 type_id: local_type_id,
1606 depth_limit: SCRYPTO_SBOR_V1_MAX_DEPTH,
1607 };
1608 write!(f, "{}", raw_value.display(display_parameters))
1609}
1610
1611impl From<FeeReserveFinalizationSummary> for TransactionFeeSummary {
1612 fn from(value: FeeReserveFinalizationSummary) -> Self {
1613 Self {
1614 total_execution_cost_units_consumed: value.total_execution_cost_units_consumed,
1615 total_finalization_cost_units_consumed: value.total_finalization_cost_units_consumed,
1616 total_execution_cost_in_xrd: value.total_execution_cost_in_xrd,
1617 total_finalization_cost_in_xrd: value.total_finalization_cost_in_xrd,
1618 total_tipping_cost_in_xrd: value.total_tipping_cost_in_xrd,
1619 total_storage_cost_in_xrd: value.total_storage_cost_in_xrd,
1620 total_royalty_cost_in_xrd: value.total_royalty_cost_in_xrd,
1621 }
1622 }
1623}
1624
1625impl TransactionFeeSummary {
1626 pub fn total_cost(&self) -> Decimal {
1627 self.total_execution_cost_in_xrd
1628 .checked_add(self.total_finalization_cost_in_xrd)
1629 .unwrap()
1630 .checked_add(self.total_tipping_cost_in_xrd)
1631 .unwrap()
1632 .checked_add(self.total_storage_cost_in_xrd)
1633 .unwrap()
1634 .checked_add(self.total_royalty_cost_in_xrd)
1635 .unwrap()
1636 }
1637
1638 pub fn network_fees(&self) -> Decimal {
1639 self.total_execution_cost_in_xrd
1640 .checked_add(self.total_finalization_cost_in_xrd)
1641 .unwrap()
1642 .checked_add(self.total_storage_cost_in_xrd)
1643 .unwrap()
1644 }
1645
1646 pub fn expected_reward_if_single_validator(&self) -> Decimal {
1651 self.expected_reward_as_proposer_if_single_validator()
1652 .checked_add(self.expected_reward_as_active_validator_if_single_validator())
1653 .unwrap()
1654 }
1655
1656 pub fn expected_reward_as_proposer_if_single_validator(&self) -> Decimal {
1657 let one_percent = Decimal::ONE_HUNDREDTH;
1658
1659 one_percent
1660 .checked_mul(TIPS_PROPOSER_SHARE_PERCENTAGE)
1661 .unwrap()
1662 .checked_mul(self.total_tipping_cost_in_xrd)
1663 .unwrap()
1664 .checked_add(
1665 one_percent
1666 .checked_mul(NETWORK_FEES_PROPOSER_SHARE_PERCENTAGE)
1667 .unwrap()
1668 .checked_mul(
1669 self.total_execution_cost_in_xrd
1670 .checked_add(self.total_finalization_cost_in_xrd)
1671 .unwrap()
1672 .checked_add(self.total_storage_cost_in_xrd)
1673 .unwrap(),
1674 )
1675 .unwrap(),
1676 )
1677 .unwrap()
1678 }
1679
1680 pub fn expected_reward_as_active_validator_if_single_validator(&self) -> Decimal {
1681 let one_percent = Decimal::ONE_HUNDREDTH;
1682
1683 one_percent
1684 .checked_mul(TIPS_VALIDATOR_SET_SHARE_PERCENTAGE)
1685 .unwrap()
1686 .checked_mul(self.total_tipping_cost_in_xrd)
1687 .unwrap()
1688 .checked_add(
1689 one_percent
1690 .checked_mul(NETWORK_FEES_VALIDATOR_SET_SHARE_PERCENTAGE)
1691 .unwrap()
1692 .checked_mul(
1693 self.total_execution_cost_in_xrd
1694 .checked_add(self.total_finalization_cost_in_xrd)
1695 .unwrap()
1696 .checked_add(self.total_storage_cost_in_xrd)
1697 .unwrap(),
1698 )
1699 .unwrap(),
1700 )
1701 .unwrap()
1702 }
1703}
1704
1705#[cfg(feature = "std")]
1706#[derive(Debug)]
1707pub enum FlamegraphError {
1708 IOError(std::io::Error),
1709 CreationError,
1710 DetailedCostBreakdownNotAvailable,
1711}
1712
1713#[cfg(test)]
1714mod tests {
1715 use radix_transactions::model::TransactionCostingParametersReceiptV2;
1716
1717 use super::*;
1718
1719 define_versioned!(
1720 #[derive(ScryptoSbor)]
1721 VersionedLocalTransactionExecution(LocalTransactionExecutionVersions) {
1722 previous_versions: [
1723 1 => LocalTransactionExecutionV1: { updates_to: 2 },
1724 ],
1725 latest_version: {
1726 2 => LocalTransactionExecution = LocalTransactionExecutionV2,
1727 },
1728 },
1729 outer_attributes: [
1730 #[derive(ScryptoSborAssertion)]
1737 #[sbor_assert(
1738 backwards_compatible(
1739 bottlenose = "FILE:node_versioned_local_transaction_execution_bottlenose.bin",
1740 cuttlefish = "FILE:node_versioned_local_transaction_execution_cuttlefish.bin"
1741 ),
1742 settings(allow_name_changes)
1743 )]
1744 ],
1745 );
1746
1747 #[derive(ScryptoSbor)]
1748 struct LocalTransactionExecutionV1 {
1749 outcome: Result<(), ScryptoOwnedRawValue>,
1750 fee_summary: TransactionFeeSummary,
1751 fee_source: FeeSource,
1752 fee_destination: FeeDestination,
1753 engine_costing_parameters: CostingParameters,
1754 transaction_costing_parameters: TransactionCostingParametersReceiptV1,
1755 application_logs: Vec<(Level, String)>,
1756 state_update_summary: StateUpdateSummary,
1757 global_balance_summary: IndexMap<GlobalAddress, IndexMap<ResourceAddress, BalanceChange>>,
1758 substates_system_structure: Vec<SubstateSystemStructure>,
1759 events_system_structure: IndexMap<EventTypeIdentifier, EventSystemStructure>,
1760 next_epoch: Option<EpochChangeEvent>,
1761 }
1762
1763 #[derive(ScryptoSbor)]
1764 struct LocalTransactionExecutionV2 {
1765 outcome: Result<(), PersistableRuntimeError>,
1766 fee_summary: TransactionFeeSummary,
1767 fee_source: FeeSource,
1768 fee_destination: FeeDestination,
1769 engine_costing_parameters: CostingParameters,
1770 transaction_costing_parameters: TransactionCostingParametersReceiptV2,
1771 application_logs: Vec<(Level, String)>,
1772 state_update_summary: StateUpdateSummary,
1773 global_balance_summary: IndexMap<GlobalAddress, IndexMap<ResourceAddress, BalanceChange>>,
1774 substates_system_structure: Vec<SubstateSystemStructure>,
1775 events_system_structure: IndexMap<EventTypeIdentifier, EventSystemStructure>,
1776 next_epoch: Option<EpochChangeEvent>,
1777 }
1778
1779 impl From<LocalTransactionExecutionV1> for LocalTransactionExecutionV2 {
1780 fn from(value: LocalTransactionExecutionV1) -> Self {
1781 Self {
1782 outcome: value.outcome.map_err(|err| PersistableRuntimeError {
1783 schema_index: 0,
1784 encoded_error: err,
1785 }),
1786 fee_summary: value.fee_summary,
1787 fee_source: value.fee_source,
1788 fee_destination: value.fee_destination,
1789 engine_costing_parameters: value.engine_costing_parameters,
1790 transaction_costing_parameters: value.transaction_costing_parameters.into(),
1791 application_logs: value.application_logs,
1792 state_update_summary: value.state_update_summary,
1793 global_balance_summary: value.global_balance_summary,
1794 substates_system_structure: value.substates_system_structure,
1795 events_system_structure: value.events_system_structure,
1796 next_epoch: value.next_epoch,
1797 }
1798 }
1799 }
1800}