Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion crates/cli/src/commands/decode.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use clap::Args;
use prism_core::types::config::NetworkConfig;
use prism_core::types::report::{DiagnosticReport, Severity};
use prism_core::{DecodeContextBuilder, OutputFormat};

#[derive(Args)]
pub struct DecodeArgs {
Expand All @@ -21,6 +22,10 @@ pub async fn run(
) -> anyhow::Result<()> {
let effective_output = if args.short { "short" } else { output_format };

let ctx = DecodeContextBuilder::from(network)
.output_format(OutputFormat::from_str(effective_output))
.build();

let report = if args.raw {
build_raw_xdr_report(&args.tx_hash)?
} else {
Expand All @@ -31,7 +36,7 @@ pub async fn run(
));
spinner.enable_steady_tick(std::time::Duration::from_millis(100));

let report = prism_core::decode::decode_transaction(&args.tx_hash, network).await?;
let report = prism_core::decode::decode_transaction(&args.tx_hash, &ctx).await?;
spinner.finish_and_clear();
report
};
Expand Down
7 changes: 6 additions & 1 deletion crates/cli/src/commands/inspect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use clap::Args;
use prism_core::types::config::NetworkConfig;
use prism_core::{DecodeContextBuilder, OutputFormat};

#[derive(Args)]
pub struct InspectArgs {
Expand All @@ -22,13 +23,17 @@ pub async fn run(
output_format: &str,
save: Option<&str>,
) -> anyhow::Result<()> {
let ctx = DecodeContextBuilder::from(network)
.output_format(OutputFormat::from_str(output_format))
.build();

let spinner = indicatif::ProgressBar::new_spinner();
spinner.set_message("Fetching and decoding transaction...");
spinner.enable_steady_tick(std::time::Duration::from_millis(100));

let report = prism_core::decode::decode_transaction_with_op_filter(
&args.tx_hash,
network,
&ctx,
args.op_index,
)
.await?;
Expand Down
9 changes: 9 additions & 0 deletions crates/core/src/decode/contract_error.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@


use crate::decode::decode_context::DecodeContext;
use crate::error::{PrismError, PrismResult};
use crate::spec::decoder;
use crate::types::address::Address;
use crate::types::config::NetworkConfig;
use crate::types::report::ContractErrorInfo;

pub async fn resolve(
contract_id: &str,
error_code: u32,
ctx: &DecodeContext,
) -> PrismResult<ContractErrorInfo> {
resolve_with_network(contract_id, error_code, &ctx.network).await
}

async fn resolve_with_network(
contract_id: &str,
error_code: u32,
network: &NetworkConfig,
Expand Down
136 changes: 136 additions & 0 deletions crates/core/src/decode/decode_context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
use crate::network::config::{Network, NetworkConfig};

/// Output format for diagnostic reports.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum OutputFormat {
#[default]
Human,
Json,
Compact,
Short,
}

impl OutputFormat {
pub fn from_str(s: &str) -> Self {
match s {
"json" => Self::Json,
"compact" => Self::Compact,
"short" => Self::Short,
_ => Self::Human,
}
}

pub fn as_str(&self) -> &'static str {
match self {
Self::Human => "human",
Self::Json => "json",
Self::Compact => "compact",
Self::Short => "short",
}
}
}

/// Runtime configuration threaded through the entire decode pipeline.
///
/// Build with [`DecodeContext::builder`] and pass by reference to every analyzer.
#[derive(Debug, Clone)]
pub struct DecodeContext {
pub network: NetworkConfig,
pub verbosity: u8,
pub output_format: OutputFormat,
}

impl DecodeContext {
/// Start building a [`DecodeContext`].
pub fn builder() -> DecodeContextBuilder {
DecodeContextBuilder::default()
}
}

/// Builder for [`DecodeContext`].
#[derive(Debug, Default)]
pub struct DecodeContextBuilder {
network: Option<NetworkConfig>,
verbosity: u8,
output_format: OutputFormat,
}

impl DecodeContextBuilder {
pub fn network(mut self, network: NetworkConfig) -> Self {
self.network = Some(network);
self
}

pub fn verbosity(mut self, verbosity: u8) -> Self {
self.verbosity = verbosity;
self
}

pub fn output_format(mut self, format: OutputFormat) -> Self {
self.output_format = format;
self
}

pub fn build(self) -> DecodeContext {
DecodeContext {
network: self.network.unwrap_or_else(NetworkConfig::testnet),
verbosity: self.verbosity,
output_format: self.output_format,
}
}
}

impl From<&NetworkConfig> for DecodeContextBuilder {
fn from(network: &NetworkConfig) -> Self {
Self::default().network(network.clone())
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn builder_defaults_to_testnet() {
let ctx = DecodeContext::builder().build();
assert_eq!(ctx.network.network, Network::Testnet);
assert_eq!(ctx.verbosity, 0);
assert_eq!(ctx.output_format, OutputFormat::Human);
}

#[test]
fn builder_sets_all_fields() {
let ctx = DecodeContext::builder()
.network(NetworkConfig::mainnet())
.verbosity(2)
.output_format(OutputFormat::Json)
.build();

assert_eq!(ctx.network.network, Network::Mainnet);
assert_eq!(ctx.verbosity, 2);
assert_eq!(ctx.output_format, OutputFormat::Json);
}

#[test]
fn output_format_roundtrip() {
for (s, expected) in [
("human", OutputFormat::Human),
("json", OutputFormat::Json),
("compact", OutputFormat::Compact),
("short", OutputFormat::Short),
("unknown", OutputFormat::Human),
] {
assert_eq!(OutputFormat::from_str(s), expected);
if s != "unknown" {
assert_eq!(expected.as_str(), s);
}
}
}

#[test]
fn builder_from_network_config_ref() {
let network = NetworkConfig::mainnet();
let ctx = DecodeContextBuilder::from(&network).build();
assert_eq!(ctx.network.network, Network::Mainnet);
}
}
13 changes: 8 additions & 5 deletions crates/core/src/decode/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@

pub mod context;
pub mod contract_error;
pub mod decode_context;
pub mod diagnostic;
pub mod host_error;
pub mod mappings;
pub mod report;

pub use decode_context::{DecodeContext, DecodeContextBuilder, OutputFormat};

use crate::error::PrismResult;
use crate::types::report::DiagnosticReport;

Expand Down Expand Up @@ -43,17 +46,17 @@ fn filter_transaction_by_operation(

pub async fn decode_transaction(
tx_hash: &str,
network: &crate::types::config::NetworkConfig,
ctx: &DecodeContext,
) -> PrismResult<DiagnosticReport> {
decode_transaction_with_op_filter(tx_hash, network, None).await
decode_transaction_with_op_filter(tx_hash, ctx, None).await
}

pub async fn decode_transaction_with_op_filter(
tx_hash: &str,
network: &crate::types::config::NetworkConfig,
ctx: &DecodeContext,
op_index: Option<usize>,
) -> PrismResult<DiagnosticReport> {
let rpc = crate::rpc::SorobanRpcClient::new(network);
let rpc = crate::rpc::SorobanRpcClient::new(&ctx.network);
let tx_data = rpc.get_transaction(tx_hash).await?;
let mut tx_data = serde_json::to_value(tx_data)
.map_err(|e| crate::error::PrismError::Internal(e.to_string()))?;
Expand All @@ -70,7 +73,7 @@ pub async fn decode_transaction_with_op_filter(
if let Ok(contract_info) = contract_error::resolve(
&error_info.contract_id.unwrap_or_default(),
error_info.error_code,
network,
ctx,
)
.await
{
Expand Down
1 change: 1 addition & 0 deletions crates/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub use types::address::Address;
pub use types::config::NetworkConfig;
pub use error::{PrismError, PrismResult};
pub use types::report::DiagnosticReport;
pub use decode::{DecodeContext, DecodeContextBuilder, OutputFormat};

pub const VERSION: &str = env!("CARGO_PKG_VERSION");

Expand Down