openzeppelin_monitor/models/blockchain/stellar/
transaction.rs1use std::ops::Deref;
7
8use base64::Engine;
9use serde::{Deserialize, Serialize};
10use serde_json;
11use stellar_xdr::curr::{Limits, ReadXdr, TransactionEnvelope, TransactionMeta, TransactionResult};
12
13#[derive(Debug, Serialize, Deserialize, Clone, Default)]
18pub struct TransactionInfo {
19 pub status: String,
22
23 #[serde(rename = "txHash")]
25 pub transaction_hash: String,
26
27 #[serde(rename = "applicationOrder")]
29 pub application_order: i32,
30
31 #[serde(rename = "feeBump")]
33 pub fee_bump: bool,
34
35 #[serde(rename = "envelopeXdr", skip_serializing_if = "Option::is_none")]
38 pub envelope_xdr: Option<String>,
39
40 #[serde(rename = "envelopeJson", skip_serializing_if = "Option::is_none")]
42 pub envelope_json: Option<serde_json::Value>,
43
44 #[serde(rename = "resultXdr", skip_serializing_if = "Option::is_none")]
46 pub result_xdr: Option<String>,
47
48 #[serde(rename = "resultJson", skip_serializing_if = "Option::is_none")]
50 pub result_json: Option<serde_json::Value>,
51
52 #[serde(rename = "resultMetaXdr", skip_serializing_if = "Option::is_none")]
54 pub result_meta_xdr: Option<String>,
55
56 #[serde(rename = "resultMetaJson", skip_serializing_if = "Option::is_none")]
58 pub result_meta_json: Option<serde_json::Value>,
59
60 #[serde(
63 rename = "diagnosticEventsXdr",
64 skip_serializing_if = "Option::is_none"
65 )]
66 pub diagnostic_events_xdr: Option<Vec<String>>,
67
68 #[serde(
70 rename = "diagnosticEventsJson",
71 skip_serializing_if = "Option::is_none"
72 )]
73 pub diagnostic_events_json: Option<Vec<serde_json::Value>>,
74
75 pub ledger: u32,
78
79 #[serde(rename = "createdAt")]
81 pub ledger_close_time: i64,
82
83 pub decoded: Option<DecodedTransaction>,
86}
87
88#[derive(Debug, Serialize, Deserialize, Clone)]
94pub struct DecodedTransaction {
95 pub envelope: Option<TransactionEnvelope>,
97
98 pub result: Option<TransactionResult>,
100
101 pub meta: Option<TransactionMeta>,
103}
104
105#[derive(Debug, Serialize, Deserialize, Clone)]
110pub struct Transaction(pub TransactionInfo);
111
112impl Transaction {
113 pub fn hash(&self) -> &String {
115 &self.0.transaction_hash
116 }
117
118 pub fn decoded(&self) -> Option<&DecodedTransaction> {
123 self.0.decoded.as_ref()
124 }
125
126 fn decode_xdr(xdr: &str) -> Option<Vec<u8>> {
131 base64::engine::general_purpose::STANDARD.decode(xdr).ok()
132 }
133}
134
135impl From<TransactionInfo> for Transaction {
136 fn from(tx: TransactionInfo) -> Self {
137 let decoded = DecodedTransaction {
138 envelope: tx
139 .envelope_xdr
140 .as_ref()
141 .and_then(|xdr| Self::decode_xdr(xdr))
142 .and_then(|bytes| TransactionEnvelope::from_xdr(bytes, Limits::none()).ok()),
143
144 result: tx
145 .result_xdr
146 .as_ref()
147 .and_then(|xdr| Self::decode_xdr(xdr))
148 .and_then(|bytes| TransactionResult::from_xdr(bytes, Limits::none()).ok()),
149
150 meta: tx
151 .result_meta_xdr
152 .as_ref()
153 .and_then(|xdr| Self::decode_xdr(xdr))
154 .and_then(|bytes| TransactionMeta::from_xdr(bytes, Limits::none()).ok()),
155 };
156
157 Self(TransactionInfo {
158 decoded: Some(decoded),
159 ..tx
160 })
161 }
162}
163
164impl Deref for Transaction {
165 type Target = TransactionInfo;
166
167 fn deref(&self) -> &Self::Target {
168 &self.0
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175 use base64::Engine;
176
177 #[test]
178 fn test_transaction_wrapper_methods() {
179 let tx_info = TransactionInfo {
180 transaction_hash: "test_hash".to_string(),
181 status: "SUCCESS".to_string(),
182 ..Default::default()
183 };
184
185 let transaction = Transaction(tx_info);
186
187 assert_eq!(transaction.hash(), "test_hash");
188 assert!(transaction.decoded().is_none());
189 }
190
191 #[test]
192 fn test_decode_xdr() {
193 let test_bytes = vec![1, 2, 3, 4];
195 let encoded = base64::engine::general_purpose::STANDARD.encode(&test_bytes);
196
197 let decoded = Transaction::decode_xdr(&encoded);
199 assert!(decoded.is_some());
200 assert_eq!(decoded.unwrap(), test_bytes);
201
202 let invalid_base64 = "invalid@@base64";
204 let result = Transaction::decode_xdr(invalid_base64);
205 assert!(result.is_none());
206 }
207
208 #[test]
209 fn test_transaction_from_info() {
210 let tx_info = TransactionInfo {
211 transaction_hash: "test_hash".to_string(),
212 status: "SUCCESS".to_string(),
213 envelope_xdr: Some("AAAA".to_string()),
214 result_xdr: Some("BBBB".to_string()),
215 result_meta_xdr: Some("CCCC".to_string()),
216 ..Default::default()
217 };
218
219 let transaction = Transaction::from(tx_info);
220
221 assert_eq!(transaction.hash(), "test_hash");
223 assert!(transaction.decoded().is_some());
224
225 let decoded = transaction.decoded().unwrap();
226 assert!(decoded.envelope.is_none());
227 assert!(decoded.result.is_none());
228 assert!(decoded.meta.is_none());
229 }
230
231 #[test]
232 fn test_transaction_deref() {
233 let tx_info = TransactionInfo {
234 transaction_hash: "test_hash".to_string(),
235 status: "SUCCESS".to_string(),
236 application_order: 1,
237 fee_bump: false,
238 ledger: 123,
239 ledger_close_time: 1234567890,
240 ..Default::default()
241 };
242
243 let transaction = Transaction(tx_info);
244
245 assert_eq!(transaction.transaction_hash, "test_hash");
247 assert_eq!(transaction.status, "SUCCESS");
248 assert_eq!(transaction.application_order, 1);
249 assert!(!transaction.fee_bump);
250 assert_eq!(transaction.ledger, 123);
251 assert_eq!(transaction.ledger_close_time, 1234567890);
252 }
253}