kora_lib/transaction/
versioned_message.rs

1use solana_message::VersionedMessage;
2
3use crate::error::KoraError;
4use base64::{engine::general_purpose::STANDARD, Engine as _};
5
6pub trait VersionedMessageExt {
7    fn encode_b64_message(&self) -> Result<String, KoraError>;
8}
9
10impl VersionedMessageExt for VersionedMessage {
11    fn encode_b64_message(&self) -> Result<String, KoraError> {
12        let serialized = self.serialize();
13        Ok(STANDARD.encode(serialized))
14    }
15}
16
17#[cfg(test)]
18mod tests {
19    use super::*;
20    use solana_message::{compiled_instruction::CompiledInstruction, v0, Message};
21    use solana_sdk::{
22        hash::Hash,
23        instruction::{AccountMeta, Instruction},
24        pubkey::Pubkey,
25        signature::Keypair,
26        signer::Signer as _,
27    };
28
29    #[test]
30    fn test_encode_b64_message_legacy() {
31        let keypair = Keypair::new();
32        let program_id = Pubkey::new_unique();
33        let instruction = Instruction::new_with_bytes(
34            program_id,
35            &[1, 2, 3, 4, 5],
36            vec![AccountMeta::new(keypair.pubkey(), true)],
37        );
38
39        let message =
40            VersionedMessage::Legacy(Message::new(&[instruction], Some(&keypair.pubkey())));
41
42        let encoded = message.encode_b64_message().unwrap();
43
44        // Verify we can decode the base64 back to bytes
45        let decoded_bytes = STANDARD.decode(&encoded).unwrap();
46        assert!(!decoded_bytes.is_empty());
47
48        // Verify it matches the original serialized message
49        let original_bytes = message.serialize();
50        assert_eq!(decoded_bytes, original_bytes);
51    }
52
53    #[test]
54    fn test_encode_b64_message_v0() {
55        let keypair = Keypair::new();
56        let program_id = Pubkey::new_unique();
57        let recipient = Pubkey::new_unique();
58
59        let v0_message = v0::Message {
60            header: solana_message::MessageHeader {
61                num_required_signatures: 1,
62                num_readonly_signed_accounts: 0,
63                num_readonly_unsigned_accounts: 2,
64            },
65            account_keys: vec![keypair.pubkey(), recipient, program_id],
66            recent_blockhash: Hash::new_unique(),
67            instructions: vec![CompiledInstruction {
68                program_id_index: 2,
69                accounts: vec![0, 1],
70                data: vec![1, 2, 3],
71            }],
72            address_table_lookups: vec![],
73        };
74
75        let message = VersionedMessage::V0(v0_message);
76
77        let encoded = message.encode_b64_message().unwrap();
78
79        // Verify we can decode the base64 back to bytes
80        let decoded_bytes = STANDARD.decode(&encoded).unwrap();
81        assert!(!decoded_bytes.is_empty());
82
83        // Verify it matches the original serialized message
84        let original_bytes = message.serialize();
85        assert_eq!(decoded_bytes, original_bytes);
86    }
87
88    #[test]
89    fn test_encode_b64_message_v0_with_lookup_tables() {
90        let keypair = Keypair::new();
91        let program_id = Pubkey::new_unique();
92        let lookup_table_account = Pubkey::new_unique();
93
94        let v0_message = v0::Message {
95            header: solana_message::MessageHeader {
96                num_required_signatures: 1,
97                num_readonly_signed_accounts: 0,
98                num_readonly_unsigned_accounts: 1,
99            },
100            account_keys: vec![keypair.pubkey(), program_id],
101            recent_blockhash: Hash::new_unique(),
102            instructions: vec![CompiledInstruction {
103                program_id_index: 1,
104                accounts: vec![0, 2], // Account at index 2 will come from lookup table
105                data: vec![42, 0, 1, 2],
106            }],
107            address_table_lookups: vec![solana_message::v0::MessageAddressTableLookup {
108                account_key: lookup_table_account,
109                writable_indexes: vec![0],
110                readonly_indexes: vec![],
111            }],
112        };
113
114        let message = VersionedMessage::V0(v0_message);
115
116        let encoded = message.encode_b64_message().unwrap();
117
118        // Verify we can decode the base64 back to bytes
119        let decoded_bytes = STANDARD.decode(&encoded).unwrap();
120        assert!(!decoded_bytes.is_empty());
121
122        // Verify it matches the original serialized message
123        let original_bytes = message.serialize();
124        assert_eq!(decoded_bytes, original_bytes);
125
126        // Verify lookup table data is preserved in original message
127        match message {
128            VersionedMessage::V0(v0_msg) => {
129                assert_eq!(v0_msg.address_table_lookups.len(), 1);
130                assert_eq!(v0_msg.address_table_lookups[0].account_key, lookup_table_account);
131                assert_eq!(v0_msg.address_table_lookups[0].writable_indexes, vec![0]);
132                assert_eq!(v0_msg.address_table_lookups[0].readonly_indexes.len(), 0);
133            }
134            _ => panic!("Expected V0 message"),
135        }
136    }
137}