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
8 changes: 8 additions & 0 deletions crates/core/src/decode/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub fn enrich_report(
ledger_sequence,
function_name: extract_function_name(tx_data),
arguments: extract_arguments(tx_data),
return_value: extract_return_value(tx_data),
fee: extract_fee_breakdown(tx_data),
resources: extract_resource_summary(tx_data),
};
Expand All @@ -43,6 +44,13 @@ fn extract_arguments(tx_data: &serde_json::Value) -> Vec<String> {
.unwrap_or_default()
}

fn extract_return_value(tx_data: &serde_json::Value) -> Option<String> {
tx_data
.get("returnValue")
.and_then(|r| r.as_str())
.map(std::string::ToString::to_string)
}

fn extract_fee_breakdown(tx_data: &serde_json::Value) -> FeeBreakdown {
FeeBreakdown {
inclusion_fee: tx_data
Expand Down
66 changes: 66 additions & 0 deletions crates/core/src/decode/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,70 @@ pub mod report;

use crate::error::PrismResult;
use crate::types::report::DiagnosticReport;
use crate::xdr::codec::XdrCodec;
use stellar_xdr::curr::{ScVal, TransactionMeta, TransactionResult};

/// Decode `resultMetaXdr` as `TransactionMeta` and, if it is V3, inject the
/// Soroban contract events, diagnostic events, and return value into the JSON
/// payload so downstream enrichment code sees the same shape it does for V1/V2.
///
/// Also extracts `fee_charged` from `resultXdr` so fee details are not lost.
fn parse_v3_metadata(tx_data: &mut serde_json::Value) -> PrismResult<()> {
// Inject fee_charged from TransactionResult regardless of V3.
if let Some(result_b64) = tx_data.get("resultXdr").and_then(|r| r.as_str()) {
if let Ok(tx_result) = TransactionResult::from_xdr_base64(result_b64) {
tx_data["inclusionFee"] = serde_json::json!(tx_result.fee_charged);
}
}

let meta_b64 = match tx_data.get("resultMetaXdr").and_then(|r| r.as_str()) {
Some(s) => s.to_string(),
None => return Ok(()),
};

let meta = match TransactionMeta::from_xdr_base64(&meta_b64) {
Ok(m) => m,
Err(_) => return Ok(()),
};

if let TransactionMeta::V3(v3) = meta {
let soroban_meta = match v3.soroban_meta {
Some(s) => s,
None => return Ok(()),
};

// Inject contract events as base64 XDR strings.
if !soroban_meta.events.is_empty() {
let contract_events: Vec<String> = soroban_meta
.events
.iter()
.filter_map(|e| XdrCodec::to_xdr_base64(e).ok())
.collect();
tx_data["events"] = serde_json::json!({
"contractEventsXdr": contract_events
});
}

// Inject diagnostic events as base64 XDR strings.
if !soroban_meta.diagnostic_events.is_empty() {
let diagnostic_events: Vec<String> = soroban_meta
.diagnostic_events
.iter()
.filter_map(|e| XdrCodec::to_xdr_base64(e).ok())
.collect();
tx_data["diagnosticEventsXdr"] = serde_json::json!(diagnostic_events);
}

// Encode the return value as a base64 XDR string.
if soroban_meta.return_value != ScVal::Void {
if let Ok(b64) = XdrCodec::to_xdr_base64(&soroban_meta.return_value) {
tx_data["returnValue"] = serde_json::json!(b64);
}
}
}

Ok(())
}

fn filter_transaction_by_operation(
tx_data: &mut serde_json::Value,
Expand Down Expand Up @@ -58,6 +122,8 @@ pub async fn decode_transaction_with_op_filter(
let mut tx_data = serde_json::to_value(tx_data)
.map_err(|e| crate::error::PrismError::Internal(e.to_string()))?;

parse_v3_metadata(&mut tx_data)?;

if let Some(index) = op_index {
filter_transaction_by_operation(&mut tx_data, index)?;
}
Expand Down
2 changes: 2 additions & 0 deletions crates/core/src/types/report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ pub struct TransactionContext {

pub arguments: Vec<String>,

pub return_value: Option<String>,

pub fee: FeeBreakdown,

pub resources: ResourceSummary,
Expand Down
42 changes: 40 additions & 2 deletions crates/core/src/xdr/codec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
use crate::error::{PrismError, PrismResult};
use base64::{engine::general_purpose::STANDARD, Engine as _};
use stellar_xdr::curr::{
DiagnosticEvent, LedgerEntry, Limits, ReadXdr, ScVec, TransactionEnvelope, TransactionMeta,
WriteXdr, TransactionResult,
ContractEvent, DiagnosticEvent, LedgerEntry, Limits, ReadXdr, ScVal, ScVec,
TransactionEnvelope, TransactionMeta, WriteXdr, TransactionResult,
};

pub trait XdrCodec: Sized {
Expand Down Expand Up @@ -144,6 +144,44 @@ impl XdrCodec for ScVec {
}
}

impl XdrCodec for ContractEvent {
const TYPE_NAME: &'static str = "ContractEvent";

fn from_xdr_bytes(bytes: &[u8]) -> PrismResult<Self> {
ContractEvent::from_xdr(bytes, Limits::none()).map_err(|e| {
PrismError::XdrDecodingFailed {
type_name: Self::TYPE_NAME,
reason: e.to_string(),
}
})
}

fn to_xdr_bytes(&self) -> PrismResult<Vec<u8>> {
self.to_xdr(Limits::none()).map_err(|e| {
PrismError::XdrError(format!("Failed to encode {}: {}", Self::TYPE_NAME, e))
})
}
}

impl XdrCodec for ScVal {
const TYPE_NAME: &'static str = "ScVal";

fn from_xdr_bytes(bytes: &[u8]) -> PrismResult<Self> {
ScVal::from_xdr(bytes, Limits::none()).map_err(|e| {
PrismError::XdrDecodingFailed {
type_name: Self::TYPE_NAME,
reason: e.to_string(),
}
})
}

fn to_xdr_bytes(&self) -> PrismResult<Vec<u8>> {
self.to_xdr(Limits::none()).map_err(|e| {
PrismError::XdrError(format!("Failed to encode {}: {}", Self::TYPE_NAME, e))
})
}
}

/// Decode a base64-encoded XDR string to raw bytes.
pub fn decode_xdr_base64(xdr_base64: &str) -> PrismResult<Vec<u8>> {
STANDARD.decode(xdr_base64).map_err(|e| {
Expand Down