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
2 changes: 1 addition & 1 deletion crates/cli/src/commands/inspect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ pub async fn run(
{
let fee_context = report.transaction_context.as_ref().map(|ctx| &ctx.fee);

let bid_fee: Option<i64> = None;
let bid_fee = fee_context.and_then(|fee| fee.bid_fee);
let resource_fee = fee_context.map(|fee| fee.resource_fee);
let total_charged_fee =
fee_context.and_then(|fee| fee.inclusion_fee.checked_add(fee.resource_fee));
Expand Down
1 change: 1 addition & 0 deletions crates/cli/src/output/renderers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,7 @@ mod tests {
resource_fee: 50,
refundable_fee: 25,
non_refundable_fee: 25,
bid_fee: Some(150),
},
resources: ResourceSummary {
cpu_instructions_used: 1000,
Expand Down
191 changes: 175 additions & 16 deletions crates/core/src/decode/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,23 +44,76 @@ fn extract_arguments(tx_data: &serde_json::Value) -> Vec<String> {
}

fn extract_fee_breakdown(tx_data: &serde_json::Value) -> FeeBreakdown {
use crate::xdr::codec::XdrCodec;
use stellar_xdr::curr::{TransactionEnvelope, TransactionResult, TransactionMeta};

// 1. Get total fee from resultXdr
let mut total_fee = 0;
if let Some(result_xdr_b64) = tx_data.get("resultXdr").and_then(|v| v.as_str()) {
if let Ok(tx_result) = TransactionResult::from_xdr_base64(result_xdr_b64) {
total_fee = tx_result.fee_charged;
}
}

// 2. Get bid fee from envelopeXdr
let mut bid_fee = None;
if let Some(envelope_xdr_b64) = tx_data.get("envelopeXdr").and_then(|v| v.as_str()) {
if let Ok(tx_envelope) = TransactionEnvelope::from_xdr_base64(envelope_xdr_b64) {
match tx_envelope {
TransactionEnvelope::Tx(v1) => {
bid_fee = Some(v1.tx.fee as i64);
}
TransactionEnvelope::TxFeeBump(fee_bump) => {
bid_fee = Some(fee_bump.tx.fee as i64);
}
TransactionEnvelope::TxV0(v0) => {
bid_fee = Some(v0.tx.fee as i64);
}
}
}
}

// 3. Get resource fee components from resultMetaXdr
let mut non_refundable_fee = 0;
let mut refundable_fee = 0;
let mut rent_fee = 0;
let mut has_soroban_meta = false;

if let Some(meta_xdr_b64) = tx_data.get("resultMetaXdr").and_then(|v| v.as_str()) {
if let Ok(tx_meta) = TransactionMeta::from_xdr_base64(meta_xdr_b64) {
match tx_meta {
TransactionMeta::V3(v3) => {
if let Some(soroban_meta) = v3.soroban_meta {
match soroban_meta.ext {
stellar_xdr::curr::SorobanTransactionMetaExt::V0 => {}
stellar_xdr::curr::SorobanTransactionMetaExt::V1(v1) => {
non_refundable_fee = v1.total_non_refundable_resource_fee_charged;
refundable_fee = v1.total_refundable_resource_fee_charged;
rent_fee = v1.rent_fee_charged;
has_soroban_meta = true;
}
}
}
}
_ => {}
}
}
}

let resource_fee = if has_soroban_meta {
non_refundable_fee + refundable_fee + rent_fee
} else {
0
};

let inclusion_fee = total_fee - resource_fee;

FeeBreakdown {
inclusion_fee: tx_data
.get("inclusionFee")
.and_then(serde_json::Value::as_i64)
.unwrap_or(0),
resource_fee: tx_data
.get("resourceFee")
.and_then(serde_json::Value::as_i64)
.unwrap_or(0),
refundable_fee: tx_data
.get("refundableFee")
.and_then(serde_json::Value::as_i64)
.unwrap_or(0),
non_refundable_fee: tx_data
.get("nonRefundableFee")
.and_then(serde_json::Value::as_i64)
.unwrap_or(0),
inclusion_fee,
resource_fee,
refundable_fee: refundable_fee + rent_fee,
non_refundable_fee,
bid_fee,
}
}

Expand All @@ -74,3 +127,109 @@ fn extract_resource_summary(_tx_data: &serde_json::Value) -> ResourceSummary {
write_bytes: 0,
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::xdr::codec::XdrCodec;
use stellar_xdr::curr::{
Memo, MuxedAccount, Preconditions, SequenceNumber, Transaction, TransactionEnvelope,
TransactionExt, TransactionResult, TransactionResultResult, TransactionV1Envelope, Uint256,
TransactionMeta, TransactionMetaV3, SorobanTransactionMeta, SorobanTransactionMetaExt,
SorobanTransactionMetaExtV1, ExtensionPoint,
};

#[test]
fn test_extract_fee_breakdown_non_soroban() {
let tx = Transaction {
source_account: MuxedAccount::Ed25519(Uint256([0; 32])),
fee: 150,
seq_num: SequenceNumber(1),
cond: Preconditions::None,
memo: Memo::None,
operations: vec![].try_into().unwrap(),
ext: TransactionExt::V0,
};
let envelope = TransactionEnvelope::Tx(TransactionV1Envelope {
tx,
signatures: vec![].try_into().unwrap(),
});
let envelope_xdr = envelope.to_xdr_base64().unwrap();

let result = TransactionResult {
fee_charged: 120,
result: TransactionResultResult::TxSuccess(vec![].try_into().unwrap()),
ext: stellar_xdr::curr::TransactionResultExt::V0,
};
let result_xdr = result.to_xdr_base64().unwrap();

let tx_data = serde_json::json!({
"envelopeXdr": envelope_xdr,
"resultXdr": result_xdr,
});

let breakdown = extract_fee_breakdown(&tx_data);
assert_eq!(breakdown.bid_fee, Some(150));
assert_eq!(breakdown.inclusion_fee, 120);
assert_eq!(breakdown.resource_fee, 0);
assert_eq!(breakdown.refundable_fee, 0);
assert_eq!(breakdown.non_refundable_fee, 0);
}

#[test]
fn test_extract_fee_breakdown_soroban() {
let tx = Transaction {
source_account: MuxedAccount::Ed25519(Uint256([0; 32])),
fee: 500,
seq_num: SequenceNumber(1),
cond: Preconditions::None,
memo: Memo::None,
operations: vec![].try_into().unwrap(),
ext: TransactionExt::V0,
};
let envelope = TransactionEnvelope::Tx(TransactionV1Envelope {
tx,
signatures: vec![].try_into().unwrap(),
});
let envelope_xdr = envelope.to_xdr_base64().unwrap();

let result = TransactionResult {
fee_charged: 450,
result: TransactionResultResult::TxSuccess(vec![].try_into().unwrap()),
ext: stellar_xdr::curr::TransactionResultExt::V0,
};
let result_xdr = result.to_xdr_base64().unwrap();

let meta = TransactionMeta::V3(TransactionMetaV3 {
ext: ExtensionPoint::V0,
tx_changes_before: vec![].try_into().unwrap(),
operations: vec![].try_into().unwrap(),
tx_changes_after: vec![].try_into().unwrap(),
soroban_meta: Some(SorobanTransactionMeta {
ext: SorobanTransactionMetaExt::V1(SorobanTransactionMetaExtV1 {
ext: ExtensionPoint::V0,
total_non_refundable_resource_fee_charged: 100,
total_refundable_resource_fee_charged: 200,
rent_fee_charged: 50,
}),
events: vec![].try_into().unwrap(),
return_value: stellar_xdr::curr::ScVal::Void,
diagnostic_events: vec![].try_into().unwrap(),
}),
});
let meta_xdr = meta.to_xdr_base64().unwrap();

let tx_data = serde_json::json!({
"envelopeXdr": envelope_xdr,
"resultXdr": result_xdr,
"resultMetaXdr": meta_xdr,
});

let breakdown = extract_fee_breakdown(&tx_data);
assert_eq!(breakdown.bid_fee, Some(500));
assert_eq!(breakdown.resource_fee, 350); // 100 + 200 + 50
assert_eq!(breakdown.inclusion_fee, 100); // 450 - 350
assert_eq!(breakdown.refundable_fee, 250); // 200 + 50
assert_eq!(breakdown.non_refundable_fee, 100);
}
}
1 change: 1 addition & 0 deletions crates/core/src/types/report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ pub struct FeeBreakdown {
pub resource_fee: i64,
pub refundable_fee: i64,
pub non_refundable_fee: i64,
pub bid_fee: Option<i64>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
Expand Down
17 changes: 16 additions & 1 deletion scratch/xdr_test/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
use stellar_xdr::curr::TransactionEnvelope;

fn main() {
println!("Hello, world!");
// We match on TransactionEnvelope to verify variants and fields.
// This is to make sure we can extract the max bid fee correctly.
let env = TransactionEnvelope::Tx(todo!());
match env {
TransactionEnvelope::Tx(ref envelope) => {
let _ = envelope.tx.fee;
}
TransactionEnvelope::TxFeeBump(ref envelope) => {
let _ = envelope.tx.fee;
}
TransactionEnvelope::TxV0(ref envelope) => {
let _ = envelope.tx.fee;
}
}
}