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