1use {
2 crate::cli_output::CliSignatureVerificationStatus,
3 chrono::{DateTime, Local, NaiveDateTime, SecondsFormat, TimeZone, Utc},
4 console::style,
5 indicatif::{ProgressBar, ProgressStyle},
6 safecoin_cli_config::SettingType,
7 solana_sdk::{
8 clock::UnixTimestamp,
9 hash::Hash,
10 instruction::CompiledInstruction,
11 message::v0::MessageAddressTableLookup,
12 native_token::lamports_to_sol,
13 program_utils::limited_deserialize,
14 pubkey::Pubkey,
15 signature::Signature,
16 stake,
17 transaction::{TransactionError, TransactionVersion, VersionedTransaction},
18 },
19 safecoin_transaction_status::{
20 Rewards, UiReturnDataEncoding, UiTransactionReturnData, UiTransactionStatusMeta,
21 },
22 safe_memo::{id as safe_memo_id, v1::id as safe_memo_v1_id},
23 std::{collections::HashMap, fmt, io},
24};
25
26#[derive(Clone, Debug)]
27pub struct BuildBalanceMessageConfig {
28 pub use_lamports_unit: bool,
29 pub show_unit: bool,
30 pub trim_trailing_zeros: bool,
31}
32
33impl Default for BuildBalanceMessageConfig {
34 fn default() -> Self {
35 Self {
36 use_lamports_unit: false,
37 show_unit: true,
38 trim_trailing_zeros: true,
39 }
40 }
41}
42
43fn is_memo_program(k: &Pubkey) -> bool {
44 let k_str = k.to_string();
45 (k_str == safe_memo_v1_id().to_string()) || (k_str == safe_memo_id().to_string())
46}
47
48pub fn build_balance_message_with_config(
49 lamports: u64,
50 config: &BuildBalanceMessageConfig,
51) -> String {
52 let value = if config.use_lamports_unit {
53 lamports.to_string()
54 } else {
55 let sol = lamports_to_sol(lamports);
56 let sol_str = format!("{:.9}", sol);
57 if config.trim_trailing_zeros {
58 sol_str
59 .trim_end_matches('0')
60 .trim_end_matches('.')
61 .to_string()
62 } else {
63 sol_str
64 }
65 };
66 let unit = if config.show_unit {
67 if config.use_lamports_unit {
68 let ess = if lamports == 1 { "" } else { "s" };
69 format!(" lamport{}", ess)
70 } else {
71 " SAFE".to_string()
72 }
73 } else {
74 "".to_string()
75 };
76 format!("{}{}", value, unit)
77}
78
79pub fn build_balance_message(lamports: u64, use_lamports_unit: bool, show_unit: bool) -> String {
80 build_balance_message_with_config(
81 lamports,
82 &BuildBalanceMessageConfig {
83 use_lamports_unit,
84 show_unit,
85 ..BuildBalanceMessageConfig::default()
86 },
87 )
88}
89
90pub fn println_name_value(name: &str, value: &str) {
92 let styled_value = if value.is_empty() {
93 style("(not set)").italic()
94 } else {
95 style(value)
96 };
97 println!("{} {}", style(name).bold(), styled_value);
98}
99
100pub fn writeln_name_value(f: &mut dyn fmt::Write, name: &str, value: &str) -> fmt::Result {
101 let styled_value = if value.is_empty() {
102 style("(not set)").italic()
103 } else {
104 style(value)
105 };
106 writeln!(f, "{} {}", style(name).bold(), styled_value)
107}
108
109pub fn println_name_value_or(name: &str, value: &str, setting_type: SettingType) {
110 let description = match setting_type {
111 SettingType::Explicit => "",
112 SettingType::Computed => "(computed)",
113 SettingType::SystemDefault => "(default)",
114 };
115
116 println!(
117 "{} {} {}",
118 style(name).bold(),
119 style(value),
120 style(description).italic(),
121 );
122}
123
124pub fn format_labeled_address(pubkey: &str, address_labels: &HashMap<String, String>) -> String {
125 let label = address_labels.get(pubkey);
126 match label {
127 Some(label) => format!(
128 "{:.31} ({:.4}..{})",
129 label,
130 pubkey,
131 pubkey.split_at(pubkey.len() - 4).1
132 ),
133 None => pubkey.to_string(),
134 }
135}
136
137pub fn println_signers(
138 blockhash: &Hash,
139 signers: &[String],
140 absent: &[String],
141 bad_sig: &[String],
142) {
143 println!();
144 println!("Blockhash: {}", blockhash);
145 if !signers.is_empty() {
146 println!("Signers (Pubkey=Signature):");
147 signers.iter().for_each(|signer| println!(" {}", signer))
148 }
149 if !absent.is_empty() {
150 println!("Absent Signers (Pubkey):");
151 absent.iter().for_each(|pubkey| println!(" {}", pubkey))
152 }
153 if !bad_sig.is_empty() {
154 println!("Bad Signatures (Pubkey):");
155 bad_sig.iter().for_each(|pubkey| println!(" {}", pubkey))
156 }
157 println!();
158}
159
160struct CliAccountMeta {
161 is_signer: bool,
162 is_writable: bool,
163 is_invoked: bool,
164}
165
166fn format_account_mode(meta: CliAccountMeta) -> String {
167 format!(
168 "{}r{}{}", if meta.is_signer {
170 "s" } else {
172 "-"
173 },
174 if meta.is_writable {
175 "w" } else {
177 "-"
178 },
179 if meta.is_invoked {
182 "x"
183 } else {
184 "-"
187 },
188 )
189}
190
191fn write_transaction<W: io::Write>(
192 w: &mut W,
193 transaction: &VersionedTransaction,
194 transaction_status: Option<&UiTransactionStatusMeta>,
195 prefix: &str,
196 sigverify_status: Option<&[CliSignatureVerificationStatus]>,
197 block_time: Option<UnixTimestamp>,
198 timezone: CliTimezone,
199) -> io::Result<()> {
200 write_block_time(w, block_time, timezone, prefix)?;
201
202 let message = &transaction.message;
203 let account_keys: Vec<AccountKeyType> = {
204 let static_keys_iter = message
205 .static_account_keys()
206 .iter()
207 .map(AccountKeyType::Known);
208 let dynamic_keys: Vec<AccountKeyType> = message
209 .address_table_lookups()
210 .map(transform_lookups_to_unknown_keys)
211 .unwrap_or_default();
212 static_keys_iter.chain(dynamic_keys).collect()
213 };
214
215 write_version(w, transaction.version(), prefix)?;
216 write_recent_blockhash(w, message.recent_blockhash(), prefix)?;
217 write_signatures(w, &transaction.signatures, sigverify_status, prefix)?;
218
219 let mut fee_payer_index = None;
220 for (account_index, account) in account_keys.iter().enumerate() {
221 if fee_payer_index.is_none() && message.is_non_loader_key(account_index) {
222 fee_payer_index = Some(account_index)
223 }
224
225 let account_meta = CliAccountMeta {
226 is_signer: message.is_signer(account_index),
227 is_writable: message.is_maybe_writable(account_index),
228 is_invoked: message.is_invoked(account_index),
229 };
230
231 write_account(
232 w,
233 account_index,
234 *account,
235 format_account_mode(account_meta),
236 Some(account_index) == fee_payer_index,
237 prefix,
238 )?;
239 }
240
241 for (instruction_index, instruction) in message.instructions().iter().enumerate() {
242 let program_pubkey = account_keys[instruction.program_id_index as usize];
243 let instruction_accounts = instruction
244 .accounts
245 .iter()
246 .map(|account_index| (account_keys[*account_index as usize], *account_index));
247
248 write_instruction(
249 w,
250 instruction_index,
251 program_pubkey,
252 instruction,
253 instruction_accounts,
254 prefix,
255 )?;
256 }
257
258 if let Some(address_table_lookups) = message.address_table_lookups() {
259 write_address_table_lookups(w, address_table_lookups, prefix)?;
260 }
261
262 if let Some(transaction_status) = transaction_status {
263 write_status(w, &transaction_status.status, prefix)?;
264 write_fees(w, transaction_status.fee, prefix)?;
265 write_balances(w, transaction_status, prefix)?;
266 write_compute_units_consumed(
267 w,
268 transaction_status.compute_units_consumed.clone().into(),
269 prefix,
270 )?;
271 write_log_messages(w, transaction_status.log_messages.as_ref().into(), prefix)?;
272 write_return_data(w, transaction_status.return_data.as_ref().into(), prefix)?;
273 write_rewards(w, transaction_status.rewards.as_ref().into(), prefix)?;
274 } else {
275 writeln!(w, "{}Status: Unavailable", prefix)?;
276 }
277
278 Ok(())
279}
280
281fn transform_lookups_to_unknown_keys(lookups: &[MessageAddressTableLookup]) -> Vec<AccountKeyType> {
282 let unknown_writable_keys = lookups
283 .iter()
284 .enumerate()
285 .flat_map(|(lookup_index, lookup)| {
286 lookup
287 .writable_indexes
288 .iter()
289 .map(move |table_index| AccountKeyType::Unknown {
290 lookup_index,
291 table_index: *table_index,
292 })
293 });
294
295 let unknown_readonly_keys = lookups
296 .iter()
297 .enumerate()
298 .flat_map(|(lookup_index, lookup)| {
299 lookup
300 .readonly_indexes
301 .iter()
302 .map(move |table_index| AccountKeyType::Unknown {
303 lookup_index,
304 table_index: *table_index,
305 })
306 });
307
308 unknown_writable_keys.chain(unknown_readonly_keys).collect()
309}
310
311enum CliTimezone {
312 Local,
313 #[allow(dead_code)]
314 Utc,
315}
316
317fn write_block_time<W: io::Write>(
318 w: &mut W,
319 block_time: Option<UnixTimestamp>,
320 timezone: CliTimezone,
321 prefix: &str,
322) -> io::Result<()> {
323 if let Some(block_time) = block_time {
324 let block_time_output = match timezone {
325 CliTimezone::Local => format!("{:?}", Local.timestamp(block_time, 0)),
326 CliTimezone::Utc => format!("{:?}", Utc.timestamp(block_time, 0)),
327 };
328 writeln!(w, "{}Block Time: {}", prefix, block_time_output,)?;
329 }
330 Ok(())
331}
332
333fn write_version<W: io::Write>(
334 w: &mut W,
335 version: TransactionVersion,
336 prefix: &str,
337) -> io::Result<()> {
338 let version = match version {
339 TransactionVersion::Legacy(_) => "legacy".to_string(),
340 TransactionVersion::Number(number) => number.to_string(),
341 };
342 writeln!(w, "{}Version: {}", prefix, version)
343}
344
345fn write_recent_blockhash<W: io::Write>(
346 w: &mut W,
347 recent_blockhash: &Hash,
348 prefix: &str,
349) -> io::Result<()> {
350 writeln!(w, "{}Recent Blockhash: {:?}", prefix, recent_blockhash)
351}
352
353fn write_signatures<W: io::Write>(
354 w: &mut W,
355 signatures: &[Signature],
356 sigverify_status: Option<&[CliSignatureVerificationStatus]>,
357 prefix: &str,
358) -> io::Result<()> {
359 let sigverify_statuses = if let Some(sigverify_status) = sigverify_status {
360 sigverify_status
361 .iter()
362 .map(|s| format!(" ({})", s))
363 .collect()
364 } else {
365 vec!["".to_string(); signatures.len()]
366 };
367 for (signature_index, (signature, sigverify_status)) in
368 signatures.iter().zip(&sigverify_statuses).enumerate()
369 {
370 writeln!(
371 w,
372 "{}Signature {}: {:?}{}",
373 prefix, signature_index, signature, sigverify_status,
374 )?;
375 }
376 Ok(())
377}
378
379#[derive(Debug, Clone, Copy, PartialEq, Eq)]
380enum AccountKeyType<'a> {
381 Known(&'a Pubkey),
382 Unknown {
383 lookup_index: usize,
384 table_index: u8,
385 },
386}
387
388impl fmt::Display for AccountKeyType<'_> {
389 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
390 match self {
391 Self::Known(address) => write!(f, "{}", address),
392 Self::Unknown {
393 lookup_index,
394 table_index,
395 } => {
396 write!(
397 f,
398 "Unknown Address (uses lookup {} and index {})",
399 lookup_index, table_index
400 )
401 }
402 }
403 }
404}
405
406fn write_account<W: io::Write>(
407 w: &mut W,
408 account_index: usize,
409 account_address: AccountKeyType,
410 account_mode: String,
411 is_fee_payer: bool,
412 prefix: &str,
413) -> io::Result<()> {
414 writeln!(
415 w,
416 "{}Account {}: {} {}{}",
417 prefix,
418 account_index,
419 account_mode,
420 account_address,
421 if is_fee_payer { " (fee payer)" } else { "" },
422 )
423}
424
425fn write_instruction<'a, W: io::Write>(
426 w: &mut W,
427 instruction_index: usize,
428 program_pubkey: AccountKeyType,
429 instruction: &CompiledInstruction,
430 instruction_accounts: impl Iterator<Item = (AccountKeyType<'a>, u8)>,
431 prefix: &str,
432) -> io::Result<()> {
433 writeln!(w, "{}Instruction {}", prefix, instruction_index)?;
434 writeln!(
435 w,
436 "{} Program: {} ({})",
437 prefix, program_pubkey, instruction.program_id_index
438 )?;
439 for (index, (account_address, account_index)) in instruction_accounts.enumerate() {
440 writeln!(
441 w,
442 "{} Account {}: {} ({})",
443 prefix, index, account_address, account_index
444 )?;
445 }
446
447 let mut raw = true;
448 if let AccountKeyType::Known(program_pubkey) = program_pubkey {
449 if program_pubkey == &solana_vote_program::id() {
450 if let Ok(vote_instruction) = limited_deserialize::<
451 solana_vote_program::vote_instruction::VoteInstruction,
452 >(&instruction.data)
453 {
454 writeln!(w, "{} {:?}", prefix, vote_instruction)?;
455 raw = false;
456 }
457 } else if program_pubkey == &stake::program::id() {
458 if let Ok(stake_instruction) =
459 limited_deserialize::<stake::instruction::StakeInstruction>(&instruction.data)
460 {
461 writeln!(w, "{} {:?}", prefix, stake_instruction)?;
462 raw = false;
463 }
464 } else if program_pubkey == &solana_sdk::system_program::id() {
465 if let Ok(system_instruction) = limited_deserialize::<
466 solana_sdk::system_instruction::SystemInstruction,
467 >(&instruction.data)
468 {
469 writeln!(w, "{} {:?}", prefix, system_instruction)?;
470 raw = false;
471 }
472 } else if is_memo_program(program_pubkey) {
473 if let Ok(s) = std::str::from_utf8(&instruction.data) {
474 writeln!(w, "{} Data: \"{}\"", prefix, s)?;
475 raw = false;
476 }
477 }
478 }
479
480 if raw {
481 writeln!(w, "{} Data: {:?}", prefix, instruction.data)?;
482 }
483
484 Ok(())
485}
486
487fn write_address_table_lookups<W: io::Write>(
488 w: &mut W,
489 address_table_lookups: &[MessageAddressTableLookup],
490 prefix: &str,
491) -> io::Result<()> {
492 for (lookup_index, lookup) in address_table_lookups.iter().enumerate() {
493 writeln!(w, "{}Address Table Lookup {}", prefix, lookup_index,)?;
494 writeln!(w, "{} Table Account: {}", prefix, lookup.account_key,)?;
495 writeln!(
496 w,
497 "{} Writable Indexes: {:?}",
498 prefix,
499 &lookup.writable_indexes[..],
500 )?;
501 writeln!(
502 w,
503 "{} Readonly Indexes: {:?}",
504 prefix,
505 &lookup.readonly_indexes[..],
506 )?;
507 }
508 Ok(())
509}
510
511fn write_rewards<W: io::Write>(
512 w: &mut W,
513 rewards: Option<&Rewards>,
514 prefix: &str,
515) -> io::Result<()> {
516 if let Some(rewards) = rewards {
517 if !rewards.is_empty() {
518 writeln!(w, "{}Rewards:", prefix,)?;
519 writeln!(
520 w,
521 "{} {:<44} {:^15} {:<16} {:<20}",
522 prefix, "Address", "Type", "Amount", "New Balance"
523 )?;
524 for reward in rewards {
525 let sign = if reward.lamports < 0 { "-" } else { "" };
526 writeln!(
527 w,
528 "{} {:<44} {:^15} {}◎{:<14.9} ◎{:<18.9}",
529 prefix,
530 reward.pubkey,
531 if let Some(reward_type) = reward.reward_type {
532 format!("{}", reward_type)
533 } else {
534 "-".to_string()
535 },
536 sign,
537 lamports_to_sol(reward.lamports.unsigned_abs()),
538 lamports_to_sol(reward.post_balance)
539 )?;
540 }
541 }
542 }
543 Ok(())
544}
545
546fn write_status<W: io::Write>(
547 w: &mut W,
548 transaction_status: &Result<(), TransactionError>,
549 prefix: &str,
550) -> io::Result<()> {
551 writeln!(
552 w,
553 "{}Status: {}",
554 prefix,
555 match transaction_status {
556 Ok(_) => "Ok".into(),
557 Err(err) => err.to_string(),
558 }
559 )
560}
561
562fn write_fees<W: io::Write>(w: &mut W, transaction_fee: u64, prefix: &str) -> io::Result<()> {
563 writeln!(w, "{} Fee: ◎{}", prefix, lamports_to_sol(transaction_fee))
564}
565
566fn write_balances<W: io::Write>(
567 w: &mut W,
568 transaction_status: &UiTransactionStatusMeta,
569 prefix: &str,
570) -> io::Result<()> {
571 assert_eq!(
572 transaction_status.pre_balances.len(),
573 transaction_status.post_balances.len()
574 );
575 for (i, (pre, post)) in transaction_status
576 .pre_balances
577 .iter()
578 .zip(transaction_status.post_balances.iter())
579 .enumerate()
580 {
581 if pre == post {
582 writeln!(
583 w,
584 "{} Account {} balance: ◎{}",
585 prefix,
586 i,
587 lamports_to_sol(*pre)
588 )?;
589 } else {
590 writeln!(
591 w,
592 "{} Account {} balance: ◎{} -> ◎{}",
593 prefix,
594 i,
595 lamports_to_sol(*pre),
596 lamports_to_sol(*post)
597 )?;
598 }
599 }
600 Ok(())
601}
602
603fn write_return_data<W: io::Write>(
604 w: &mut W,
605 return_data: Option<&UiTransactionReturnData>,
606 prefix: &str,
607) -> io::Result<()> {
608 if let Some(return_data) = return_data {
609 let (data, encoding) = &return_data.data;
610 let raw_return_data = match encoding {
611 UiReturnDataEncoding::Base64 => base64::decode(data).map_err(|err| {
612 io::Error::new(
613 io::ErrorKind::Other,
614 format!("could not parse data as {:?}: {:?}", encoding, err),
615 )
616 })?,
617 };
618 if !raw_return_data.is_empty() {
619 use pretty_hex::*;
620 writeln!(
621 w,
622 "{}Return Data from Program {}:",
623 prefix, return_data.program_id
624 )?;
625 writeln!(w, "{} {:?}", prefix, raw_return_data.hex_dump())?;
626 }
627 }
628 Ok(())
629}
630
631fn write_compute_units_consumed<W: io::Write>(
632 w: &mut W,
633 compute_units_consumed: Option<u64>,
634 prefix: &str,
635) -> io::Result<()> {
636 if let Some(cus) = compute_units_consumed {
637 writeln!(w, "{}Compute Units Consumed: {}", prefix, cus)?;
638 }
639 Ok(())
640}
641
642fn write_log_messages<W: io::Write>(
643 w: &mut W,
644 log_messages: Option<&Vec<String>>,
645 prefix: &str,
646) -> io::Result<()> {
647 if let Some(log_messages) = log_messages {
648 if !log_messages.is_empty() {
649 writeln!(w, "{}Log Messages:", prefix,)?;
650 for log_message in log_messages {
651 writeln!(w, "{} {}", prefix, log_message)?;
652 }
653 }
654 }
655 Ok(())
656}
657
658pub fn println_transaction(
659 transaction: &VersionedTransaction,
660 transaction_status: Option<&UiTransactionStatusMeta>,
661 prefix: &str,
662 sigverify_status: Option<&[CliSignatureVerificationStatus]>,
663 block_time: Option<UnixTimestamp>,
664) {
665 let mut w = Vec::new();
666 if write_transaction(
667 &mut w,
668 transaction,
669 transaction_status,
670 prefix,
671 sigverify_status,
672 block_time,
673 CliTimezone::Local,
674 )
675 .is_ok()
676 {
677 if let Ok(s) = String::from_utf8(w) {
678 print!("{}", s);
679 }
680 }
681}
682
683pub fn writeln_transaction(
684 f: &mut dyn fmt::Write,
685 transaction: &VersionedTransaction,
686 transaction_status: Option<&UiTransactionStatusMeta>,
687 prefix: &str,
688 sigverify_status: Option<&[CliSignatureVerificationStatus]>,
689 block_time: Option<UnixTimestamp>,
690) -> fmt::Result {
691 let mut w = Vec::new();
692 let write_result = write_transaction(
693 &mut w,
694 transaction,
695 transaction_status,
696 prefix,
697 sigverify_status,
698 block_time,
699 CliTimezone::Local,
700 );
701
702 if write_result.is_ok() {
703 if let Ok(s) = String::from_utf8(w) {
704 write!(f, "{}", s)?;
705 }
706 }
707 Ok(())
708}
709
710pub fn new_spinner_progress_bar() -> ProgressBar {
712 let progress_bar = ProgressBar::new(42);
713 progress_bar
714 .set_style(ProgressStyle::default_spinner().template("{spinner:.green} {wide_msg}"));
715 progress_bar.enable_steady_tick(100);
716 progress_bar
717}
718
719pub fn unix_timestamp_to_string(unix_timestamp: UnixTimestamp) -> String {
720 match NaiveDateTime::from_timestamp_opt(unix_timestamp, 0) {
721 Some(ndt) => DateTime::<Utc>::from_utc(ndt, Utc).to_rfc3339_opts(SecondsFormat::Secs, true),
722 None => format!("UnixTimestamp {}", unix_timestamp),
723 }
724}
725
726#[cfg(test)]
727mod test {
728 use {
729 super::*,
730 solana_sdk::{
731 message::{
732 v0::{self, LoadedAddresses},
733 Message as LegacyMessage, MessageHeader, VersionedMessage,
734 },
735 pubkey::Pubkey,
736 signature::{Keypair, Signer},
737 transaction::Transaction,
738 transaction_context::TransactionReturnData,
739 },
740 safecoin_transaction_status::{Reward, RewardType, TransactionStatusMeta},
741 std::io::BufWriter,
742 };
743
744 fn new_test_keypair() -> Keypair {
745 let secret = ed25519_dalek::SecretKey::from_bytes(&[0u8; 32]).unwrap();
746 let public = ed25519_dalek::PublicKey::from(&secret);
747 let keypair = ed25519_dalek::Keypair { secret, public };
748 Keypair::from_bytes(&keypair.to_bytes()).unwrap()
749 }
750
751 fn new_test_v0_transaction() -> VersionedTransaction {
752 let keypair = new_test_keypair();
753 let account_key = Pubkey::new_from_array([1u8; 32]);
754 let address_table_key = Pubkey::new_from_array([2u8; 32]);
755 VersionedTransaction::try_new(
756 VersionedMessage::V0(v0::Message {
757 header: MessageHeader {
758 num_required_signatures: 1,
759 num_readonly_signed_accounts: 0,
760 num_readonly_unsigned_accounts: 1,
761 },
762 recent_blockhash: Hash::default(),
763 account_keys: vec![keypair.pubkey(), account_key],
764 address_table_lookups: vec![MessageAddressTableLookup {
765 account_key: address_table_key,
766 writable_indexes: vec![0],
767 readonly_indexes: vec![1],
768 }],
769 instructions: vec![CompiledInstruction::new_from_raw_parts(
770 3,
771 vec![],
772 vec![1, 2],
773 )],
774 }),
775 &[&keypair],
776 )
777 .unwrap()
778 }
779
780 #[test]
781 fn test_write_legacy_transaction() {
782 let keypair = new_test_keypair();
783 let account_key = Pubkey::new_from_array([1u8; 32]);
784 let transaction = VersionedTransaction::from(Transaction::new(
785 &[&keypair],
786 LegacyMessage {
787 header: MessageHeader {
788 num_required_signatures: 1,
789 num_readonly_signed_accounts: 0,
790 num_readonly_unsigned_accounts: 1,
791 },
792 recent_blockhash: Hash::default(),
793 account_keys: vec![keypair.pubkey(), account_key],
794 instructions: vec![CompiledInstruction::new_from_raw_parts(1, vec![], vec![0])],
795 },
796 Hash::default(),
797 ));
798
799 let sigverify_status = CliSignatureVerificationStatus::verify_transaction(&transaction);
800 let meta = TransactionStatusMeta {
801 status: Ok(()),
802 fee: 5000,
803 pre_balances: vec![5000, 10_000],
804 post_balances: vec![0, 9_900],
805 inner_instructions: None,
806 log_messages: Some(vec!["Test message".to_string()]),
807 pre_token_balances: None,
808 post_token_balances: None,
809 rewards: Some(vec![Reward {
810 pubkey: account_key.to_string(),
811 lamports: -100,
812 post_balance: 9_900,
813 reward_type: Some(RewardType::Rent),
814 commission: None,
815 }]),
816 loaded_addresses: LoadedAddresses::default(),
817 return_data: Some(TransactionReturnData {
818 program_id: Pubkey::new_from_array([2u8; 32]),
819 data: vec![1, 2, 3],
820 }),
821 compute_units_consumed: Some(1234u64),
822 };
823
824 let output = {
825 let mut write_buffer = BufWriter::new(Vec::new());
826 write_transaction(
827 &mut write_buffer,
828 &transaction,
829 Some(&meta.into()),
830 "",
831 Some(&sigverify_status),
832 Some(1628633791),
833 CliTimezone::Utc,
834 )
835 .unwrap();
836 let bytes = write_buffer.into_inner().unwrap();
837 String::from_utf8(bytes).unwrap()
838 };
839
840 assert_eq!(
841 output,
842 r#"Block Time: 2021-08-10T22:16:31Z
843Version: legacy
844Recent Blockhash: 11111111111111111111111111111111
845Signature 0: 5pkjrE4VBa3Bu9CMKXgh1U345cT1gGo8QBVRTzHAo6gHeiPae5BTbShP15g6NgqRMNqu8Qrhph1ATmrfC1Ley3rx (pass)
846Account 0: srw- 4zvwRjXUKGfvwnParsHAS3HuSVzV5cA4McphgmoCtajS (fee payer)
847Account 1: -r-x 4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi
848Instruction 0
849 Program: 4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi (1)
850 Account 0: 4zvwRjXUKGfvwnParsHAS3HuSVzV5cA4McphgmoCtajS (0)
851 Data: []
852Status: Ok
853 Fee: ◎0.000005
854 Account 0 balance: ◎0.000005 -> ◎0
855 Account 1 balance: ◎0.00001 -> ◎0.0000099
856Compute Units Consumed: 1234
857Log Messages:
858 Test message
859Return Data from Program 8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR:
860 Length: 3 (0x3) bytes
8610000: 01 02 03 ...
862Rewards:
863 Address Type Amount New Balance \0
864 4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi rent -◎0.000000100 ◎0.000009900 \0
865"#.replace("\\0", "") );
867 }
868
869 #[test]
870 fn test_write_v0_transaction() {
871 let versioned_tx = new_test_v0_transaction();
872 let sigverify_status = CliSignatureVerificationStatus::verify_transaction(&versioned_tx);
873 let address_table_entry1 = Pubkey::new_from_array([3u8; 32]);
874 let address_table_entry2 = Pubkey::new_from_array([4u8; 32]);
875 let loaded_addresses = LoadedAddresses {
876 writable: vec![address_table_entry1],
877 readonly: vec![address_table_entry2],
878 };
879 let meta = TransactionStatusMeta {
880 status: Ok(()),
881 fee: 5000,
882 pre_balances: vec![5000, 10_000, 15_000, 20_000],
883 post_balances: vec![0, 10_000, 14_900, 20_000],
884 inner_instructions: None,
885 log_messages: Some(vec!["Test message".to_string()]),
886 pre_token_balances: None,
887 post_token_balances: None,
888 rewards: Some(vec![Reward {
889 pubkey: address_table_entry1.to_string(),
890 lamports: -100,
891 post_balance: 14_900,
892 reward_type: Some(RewardType::Rent),
893 commission: None,
894 }]),
895 loaded_addresses,
896 return_data: Some(TransactionReturnData {
897 program_id: Pubkey::new_from_array([2u8; 32]),
898 data: vec![1, 2, 3],
899 }),
900 compute_units_consumed: Some(2345u64),
901 };
902
903 let output = {
904 let mut write_buffer = BufWriter::new(Vec::new());
905 write_transaction(
906 &mut write_buffer,
907 &versioned_tx,
908 Some(&meta.into()),
909 "",
910 Some(&sigverify_status),
911 Some(1628633791),
912 CliTimezone::Utc,
913 )
914 .unwrap();
915 let bytes = write_buffer.into_inner().unwrap();
916 String::from_utf8(bytes).unwrap()
917 };
918
919 assert_eq!(
920 output,
921 r#"Block Time: 2021-08-10T22:16:31Z
922Version: 0
923Recent Blockhash: 11111111111111111111111111111111
924Signature 0: 5iEy3TT3ZhTA1NkuCY8GrQGNVY8d5m1bpjdh5FT3Ca4Py81fMipAZjafDuKJKrkw5q5UAAd8oPcgZ4nyXpHt4Fp7 (pass)
925Account 0: srw- 4zvwRjXUKGfvwnParsHAS3HuSVzV5cA4McphgmoCtajS (fee payer)
926Account 1: -r-- 4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi
927Account 2: -rw- Unknown Address (uses lookup 0 and index 0)
928Account 3: -r-x Unknown Address (uses lookup 0 and index 1)
929Instruction 0
930 Program: Unknown Address (uses lookup 0 and index 1) (3)
931 Account 0: 4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi (1)
932 Account 1: Unknown Address (uses lookup 0 and index 0) (2)
933 Data: []
934Address Table Lookup 0
935 Table Account: 8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR
936 Writable Indexes: [0]
937 Readonly Indexes: [1]
938Status: Ok
939 Fee: ◎0.000005
940 Account 0 balance: ◎0.000005 -> ◎0
941 Account 1 balance: ◎0.00001
942 Account 2 balance: ◎0.000015 -> ◎0.0000149
943 Account 3 balance: ◎0.00002
944Compute Units Consumed: 2345
945Log Messages:
946 Test message
947Return Data from Program 8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR:
948 Length: 3 (0x3) bytes
9490000: 01 02 03 ...
950Rewards:
951 Address Type Amount New Balance \0
952 CktRuQ2mttgRGkXJtyksdKHjUdc2C4TgDzyB98oEzy8 rent -◎0.000000100 ◎0.000014900 \0
953"#.replace("\\0", "") );
955 }
956
957 #[test]
958 fn test_format_labeled_address() {
959 let pubkey = Pubkey::default().to_string();
960 let mut address_labels = HashMap::new();
961
962 assert_eq!(format_labeled_address(&pubkey, &address_labels), pubkey);
963
964 address_labels.insert(pubkey.to_string(), "Default Address".to_string());
965 assert_eq!(
966 &format_labeled_address(&pubkey, &address_labels),
967 "Default Address (1111..1111)"
968 );
969
970 address_labels.insert(
971 pubkey.to_string(),
972 "abcdefghijklmnopqrstuvwxyz1234567890".to_string(),
973 );
974 assert_eq!(
975 &format_labeled_address(&pubkey, &address_labels),
976 "abcdefghijklmnopqrstuvwxyz12345 (1111..1111)"
977 );
978 }
979}