openzeppelin_monitor/models/blockchain/stellar/
transaction.rsuse std::ops::Deref;
use base64::Engine;
use serde::{Deserialize, Serialize};
use serde_json;
use stellar_xdr::curr::{Limits, ReadXdr, TransactionEnvelope, TransactionMeta, TransactionResult};
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct TransactionInfo {
pub status: String,
#[serde(rename = "txHash")]
pub transaction_hash: String,
#[serde(rename = "applicationOrder")]
pub application_order: i32,
#[serde(rename = "feeBump")]
pub fee_bump: bool,
#[serde(rename = "envelopeXdr", skip_serializing_if = "Option::is_none")]
pub envelope_xdr: Option<String>,
#[serde(rename = "envelopeJson", skip_serializing_if = "Option::is_none")]
pub envelope_json: Option<serde_json::Value>,
#[serde(rename = "resultXdr", skip_serializing_if = "Option::is_none")]
pub result_xdr: Option<String>,
#[serde(rename = "resultJson", skip_serializing_if = "Option::is_none")]
pub result_json: Option<serde_json::Value>,
#[serde(rename = "resultMetaXdr", skip_serializing_if = "Option::is_none")]
pub result_meta_xdr: Option<String>,
#[serde(rename = "resultMetaJson", skip_serializing_if = "Option::is_none")]
pub result_meta_json: Option<serde_json::Value>,
#[serde(
rename = "diagnosticEventsXdr",
skip_serializing_if = "Option::is_none"
)]
pub diagnostic_events_xdr: Option<Vec<String>>,
#[serde(
rename = "diagnosticEventsJson",
skip_serializing_if = "Option::is_none"
)]
pub diagnostic_events_json: Option<Vec<serde_json::Value>>,
pub ledger: u32,
#[serde(rename = "createdAt")]
pub ledger_close_time: i64,
pub decoded: Option<DecodedTransaction>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct DecodedTransaction {
pub envelope: Option<TransactionEnvelope>,
pub result: Option<TransactionResult>,
pub meta: Option<TransactionMeta>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Transaction(pub TransactionInfo);
impl Transaction {
pub fn hash(&self) -> &String {
&self.0.transaction_hash
}
pub fn decoded(&self) -> Option<&DecodedTransaction> {
self.0.decoded.as_ref()
}
fn decode_xdr(xdr: &str) -> Option<Vec<u8>> {
base64::engine::general_purpose::STANDARD.decode(xdr).ok()
}
}
impl From<TransactionInfo> for Transaction {
fn from(tx: TransactionInfo) -> Self {
let decoded = DecodedTransaction {
envelope: tx
.envelope_xdr
.as_ref()
.and_then(|xdr| Self::decode_xdr(xdr))
.and_then(|bytes| TransactionEnvelope::from_xdr(bytes, Limits::none()).ok()),
result: tx
.result_xdr
.as_ref()
.and_then(|xdr| Self::decode_xdr(xdr))
.and_then(|bytes| TransactionResult::from_xdr(bytes, Limits::none()).ok()),
meta: tx
.result_meta_xdr
.as_ref()
.and_then(|xdr| Self::decode_xdr(xdr))
.and_then(|bytes| TransactionMeta::from_xdr(bytes, Limits::none()).ok()),
};
Self(TransactionInfo {
decoded: Some(decoded),
..tx
})
}
}
impl Deref for Transaction {
type Target = TransactionInfo;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(test)]
mod tests {
use super::*;
use base64::Engine;
#[test]
fn test_transaction_wrapper_methods() {
let tx_info = TransactionInfo {
transaction_hash: "test_hash".to_string(),
status: "SUCCESS".to_string(),
..Default::default()
};
let transaction = Transaction(tx_info);
assert_eq!(transaction.hash(), "test_hash");
assert!(transaction.decoded().is_none());
}
#[test]
fn test_decode_xdr() {
let test_bytes = vec![1, 2, 3, 4];
let encoded = base64::engine::general_purpose::STANDARD.encode(&test_bytes);
let decoded = Transaction::decode_xdr(&encoded);
assert!(decoded.is_some());
assert_eq!(decoded.unwrap(), test_bytes);
let invalid_base64 = "invalid@@base64";
let result = Transaction::decode_xdr(invalid_base64);
assert!(result.is_none());
}
#[test]
fn test_transaction_from_info() {
let tx_info = TransactionInfo {
transaction_hash: "test_hash".to_string(),
status: "SUCCESS".to_string(),
envelope_xdr: Some("AAAA".to_string()),
result_xdr: Some("BBBB".to_string()),
result_meta_xdr: Some("CCCC".to_string()),
..Default::default()
};
let transaction = Transaction::from(tx_info);
assert_eq!(transaction.hash(), "test_hash");
assert!(transaction.decoded().is_some());
let decoded = transaction.decoded().unwrap();
assert!(decoded.envelope.is_none());
assert!(decoded.result.is_none());
assert!(decoded.meta.is_none());
}
#[test]
fn test_transaction_deref() {
let tx_info = TransactionInfo {
transaction_hash: "test_hash".to_string(),
status: "SUCCESS".to_string(),
application_order: 1,
fee_bump: false,
ledger: 123,
ledger_close_time: 1234567890,
..Default::default()
};
let transaction = Transaction(tx_info);
assert_eq!(transaction.transaction_hash, "test_hash");
assert_eq!(transaction.status, "SUCCESS");
assert_eq!(transaction.application_order, 1);
assert!(!transaction.fee_bump);
assert_eq!(transaction.ledger, 123);
assert_eq!(transaction.ledger_close_time, 1234567890);
}
}