1#![cfg_attr(
2 not(feature = "agave-unstable-api"),
3 deprecated(
4 since = "3.1.0",
5 note = "This crate has been marked for formal inclusion in the Agave Unstable API. From \
6 v4.0.0 onward, the `agave-unstable-api` crate feature must be specified to \
7 acknowledge use of an interface that may break without warning."
8 )
9)]
10use {
11 serde::{Deserialize, Serialize},
12 solana_account_decoder::{
13 parse_token::{real_number_string_trimmed, UiTokenAmount},
14 StringAmount,
15 },
16 solana_message::v0::LoadedAddresses,
17 solana_serde::default_on_eof,
18 solana_transaction_context::TransactionReturnData,
19 solana_transaction_error::{TransactionError, TransactionResult as Result},
20 solana_transaction_status::{
21 InnerInstructions, Reward, RewardType, TransactionStatusMeta, TransactionTokenBalance,
22 },
23 std::str::FromStr,
24};
25
26pub mod convert;
27
28pub type StoredExtendedRewards = Vec<StoredExtendedReward>;
29
30#[derive(Serialize, Deserialize)]
31pub struct StoredExtendedReward {
32 pubkey: String,
33 lamports: i64,
34 #[serde(deserialize_with = "default_on_eof")]
35 post_balance: u64,
36 #[serde(deserialize_with = "default_on_eof")]
37 reward_type: Option<RewardType>,
38 #[serde(deserialize_with = "default_on_eof")]
39 commission: Option<u8>,
40}
41
42impl From<StoredExtendedReward> for Reward {
43 fn from(value: StoredExtendedReward) -> Self {
44 let StoredExtendedReward {
45 pubkey,
46 lamports,
47 post_balance,
48 reward_type,
49 commission,
50 } = value;
51 Self {
52 pubkey,
53 lamports,
54 post_balance,
55 reward_type,
56 commission,
57 }
58 }
59}
60
61impl From<Reward> for StoredExtendedReward {
62 fn from(value: Reward) -> Self {
63 let Reward {
64 pubkey,
65 lamports,
66 post_balance,
67 reward_type,
68 commission,
69 } = value;
70 Self {
71 pubkey,
72 lamports,
73 post_balance,
74 reward_type,
75 commission,
76 }
77 }
78}
79
80#[derive(Serialize, Deserialize)]
81pub struct StoredTokenAmount {
82 pub ui_amount: f64,
83 pub decimals: u8,
84 pub amount: StringAmount,
85}
86
87impl From<StoredTokenAmount> for UiTokenAmount {
88 fn from(value: StoredTokenAmount) -> Self {
89 let StoredTokenAmount {
90 ui_amount,
91 decimals,
92 amount,
93 } = value;
94 let ui_amount_string =
95 real_number_string_trimmed(u64::from_str(&amount).unwrap_or(0), decimals);
96 Self {
97 ui_amount: Some(ui_amount),
98 decimals,
99 amount,
100 ui_amount_string,
101 }
102 }
103}
104
105impl From<UiTokenAmount> for StoredTokenAmount {
106 fn from(value: UiTokenAmount) -> Self {
107 let UiTokenAmount {
108 ui_amount,
109 decimals,
110 amount,
111 ..
112 } = value;
113 Self {
114 ui_amount: ui_amount.unwrap_or(0.0),
115 decimals,
116 amount,
117 }
118 }
119}
120
121struct StoredTransactionError(Vec<u8>);
122
123impl From<StoredTransactionError> for TransactionError {
124 fn from(value: StoredTransactionError) -> Self {
125 let bytes = value.0;
126 bincode::deserialize(&bytes).expect("transaction error to deserialize from bytes")
127 }
128}
129
130impl From<TransactionError> for StoredTransactionError {
131 fn from(value: TransactionError) -> Self {
132 let bytes = bincode::serialize(&value).expect("transaction error to serialize to bytes");
133 StoredTransactionError(bytes)
134 }
135}
136
137#[derive(Serialize, Deserialize)]
138pub struct StoredTransactionTokenBalance {
139 pub account_index: u8,
140 pub mint: String,
141 pub ui_token_amount: StoredTokenAmount,
142 #[serde(deserialize_with = "default_on_eof")]
143 pub owner: String,
144 #[serde(deserialize_with = "default_on_eof")]
145 pub program_id: String,
146}
147
148impl From<StoredTransactionTokenBalance> for TransactionTokenBalance {
149 fn from(value: StoredTransactionTokenBalance) -> Self {
150 let StoredTransactionTokenBalance {
151 account_index,
152 mint,
153 ui_token_amount,
154 owner,
155 program_id,
156 } = value;
157 Self {
158 account_index,
159 mint,
160 ui_token_amount: ui_token_amount.into(),
161 owner,
162 program_id,
163 }
164 }
165}
166
167impl From<TransactionTokenBalance> for StoredTransactionTokenBalance {
168 fn from(value: TransactionTokenBalance) -> Self {
169 let TransactionTokenBalance {
170 account_index,
171 mint,
172 ui_token_amount,
173 owner,
174 program_id,
175 } = value;
176 Self {
177 account_index,
178 mint,
179 ui_token_amount: ui_token_amount.into(),
180 owner,
181 program_id,
182 }
183 }
184}
185
186#[derive(Serialize, Deserialize)]
187pub struct StoredTransactionStatusMeta {
188 pub status: Result<()>,
189 pub fee: u64,
190 pub pre_balances: Vec<u64>,
191 pub post_balances: Vec<u64>,
192 #[serde(deserialize_with = "default_on_eof")]
193 pub inner_instructions: Option<Vec<InnerInstructions>>,
194 #[serde(deserialize_with = "default_on_eof")]
195 pub log_messages: Option<Vec<String>>,
196 #[serde(deserialize_with = "default_on_eof")]
197 pub pre_token_balances: Option<Vec<StoredTransactionTokenBalance>>,
198 #[serde(deserialize_with = "default_on_eof")]
199 pub post_token_balances: Option<Vec<StoredTransactionTokenBalance>>,
200 #[serde(deserialize_with = "default_on_eof")]
201 pub rewards: Option<Vec<StoredExtendedReward>>,
202 #[serde(deserialize_with = "default_on_eof")]
203 pub return_data: Option<TransactionReturnData>,
204 #[serde(deserialize_with = "default_on_eof")]
205 pub compute_units_consumed: Option<u64>,
206 #[serde(deserialize_with = "default_on_eof")]
207 pub cost_units: Option<u64>,
208}
209
210impl From<StoredTransactionStatusMeta> for TransactionStatusMeta {
211 fn from(value: StoredTransactionStatusMeta) -> Self {
212 let StoredTransactionStatusMeta {
213 status,
214 fee,
215 pre_balances,
216 post_balances,
217 inner_instructions,
218 log_messages,
219 pre_token_balances,
220 post_token_balances,
221 rewards,
222 return_data,
223 compute_units_consumed,
224 cost_units,
225 } = value;
226 Self {
227 status,
228 fee,
229 pre_balances,
230 post_balances,
231 inner_instructions,
232 log_messages,
233 pre_token_balances: pre_token_balances
234 .map(|balances| balances.into_iter().map(|balance| balance.into()).collect()),
235 post_token_balances: post_token_balances
236 .map(|balances| balances.into_iter().map(|balance| balance.into()).collect()),
237 rewards: rewards
238 .map(|rewards| rewards.into_iter().map(|reward| reward.into()).collect()),
239 loaded_addresses: LoadedAddresses::default(),
240 return_data,
241 compute_units_consumed,
242 cost_units,
243 }
244 }
245}
246
247impl TryFrom<TransactionStatusMeta> for StoredTransactionStatusMeta {
248 type Error = bincode::Error;
249 fn try_from(value: TransactionStatusMeta) -> std::result::Result<Self, Self::Error> {
250 let TransactionStatusMeta {
251 status,
252 fee,
253 pre_balances,
254 post_balances,
255 inner_instructions,
256 log_messages,
257 pre_token_balances,
258 post_token_balances,
259 rewards,
260 loaded_addresses,
261 return_data,
262 compute_units_consumed,
263 cost_units,
264 } = value;
265
266 if !loaded_addresses.is_empty() {
267 return Err(
270 bincode::ErrorKind::Custom("Bincode serialization is deprecated".into()).into(),
271 );
272 }
273
274 Ok(Self {
275 status,
276 fee,
277 pre_balances,
278 post_balances,
279 inner_instructions,
280 log_messages,
281 pre_token_balances: pre_token_balances
282 .map(|balances| balances.into_iter().map(|balance| balance.into()).collect()),
283 post_token_balances: post_token_balances
284 .map(|balances| balances.into_iter().map(|balance| balance.into()).collect()),
285 rewards: rewards
286 .map(|rewards| rewards.into_iter().map(|reward| reward.into()).collect()),
287 return_data,
288 compute_units_consumed,
289 cost_units,
290 })
291 }
292}
293
294#[cfg(test)]
295mod tests {
296 use {
297 crate::StoredTransactionError, solana_instruction::error::InstructionError,
298 solana_transaction_error::TransactionError, test_case::test_case,
299 };
300
301 #[test_case(TransactionError::InsufficientFundsForFee; "Named variant error")]
302 #[test_case(TransactionError::InsufficientFundsForRent { account_index: 42 }; "Struct variant error")]
303 #[test_case(TransactionError::DuplicateInstruction(42); "Single-value tuple variant error")]
304 #[test_case(TransactionError::InstructionError(42, InstructionError::Custom(0xdeadbeef)); "`InstructionError`")]
305 fn test_serialize_transaction_error_to_stored_transaction_error_round_trip(
306 err: TransactionError,
307 ) {
308 let serialized: StoredTransactionError = err.clone().into();
309 let deserialized: TransactionError = serialized.into();
310 assert_eq!(deserialized, err);
311 }
312
313 #[test_case(
314 vec![4, 0, 0, 0, ],
315 TransactionError::InsufficientFundsForFee;
316 "Named variant error"
317 )]
318 #[test_case(
319 vec![
320 31, 0, 0, 0, 42, ],
323 TransactionError::InsufficientFundsForRent { account_index: 42 };
324 "Struct variant error"
325 )]
326 #[test_case(
327 vec![
328 30, 0, 0, 0, 42, ],
331 TransactionError::DuplicateInstruction(42);
332 "Single-value tuple variant error"
333 )]
334 #[test_case(
335 vec![
336 8, 0, 0, 0, 42, 25, 0, 0, 0, 239, 190, 173, 222,
341 ],
342 TransactionError::InstructionError(42, InstructionError::Custom(0xdeadbeef));
343 "`InstructionError`"
344 )]
345 fn test_deserialize_stored_transaction_error(
346 stored_bytes: Vec<u8>,
347 expected_transaction_error: TransactionError,
348 ) {
349 let stored_transaction = StoredTransactionError(stored_bytes);
350 let deserialized: TransactionError = stored_transaction.into();
351 assert_eq!(deserialized, expected_transaction_error);
352 }
353
354 #[test_case(
355 vec![4, 0, 0, 0, ],
356 TransactionError::InsufficientFundsForFee;
357 "Named variant error"
358 )]
359 #[test_case(
360 vec![
361 31, 0, 0, 0, 42, ],
364 TransactionError::InsufficientFundsForRent { account_index: 42 };
365 "Struct variant error"
366 )]
367 #[test_case(
368 vec![
369 30, 0, 0, 0, 42, ],
372 TransactionError::DuplicateInstruction(42);
373 "Single-value tuple variant error"
374 )]
375 #[test_case(
376 vec![
377 8, 0, 0, 0, 42, 25, 0, 0, 0, 239, 190, 173, 222,
382 ],
383 TransactionError::InstructionError(42, InstructionError::Custom(0xdeadbeef));
384 "`InstructionError`"
385 )]
386 fn test_seserialize_stored_transaction_error(
387 expected_serialized_bytes: Vec<u8>,
388 transaction_error: TransactionError,
389 ) {
390 let StoredTransactionError(serialized_bytes) = transaction_error.into();
391 assert_eq!(serialized_bytes, expected_serialized_bytes);
392 }
393}