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