solana_transaction_introspection/
lib.rs

1#[cfg(feature = "anchor")]
2pub mod typed_transaction;
3#[cfg(feature = "anchor")]
4pub use typed_transaction::*;
5
6#[cfg(feature = "anchor")]
7pub mod prelude {
8    // Anchor-specific imports
9    pub use anchor_lang::prelude::*;
10    pub use borsh::BorshDeserialize;
11    pub use typed_transaction_macros::{typed_instruction, FromSignedTransaction, TypedAccounts};
12    pub use anchor_lang::{
13        Discriminator,
14        solana_program::{
15            sanitize::SanitizeError,
16            serialize_utils::{read_pubkey, read_slice, read_u16},
17            ed25519_program,
18            msg
19        }
20    };
21    pub use crate::{
22        DeserializeWithDiscriminator, FromAccountMetas, InstructionOwner, SignedInstruction,
23        SignedTransaction, TransactionHeader, TypedInstruction, VariableDiscriminator,
24    };
25}
26
27// If the "anchor" feature is not enabled
28#[cfg(not(feature = "anchor"))]
29pub mod prelude {
30    pub use solana_program::{
31        sanitize::SanitizeError,
32        serialize_utils::{read_pubkey, read_slice, read_u16},
33        ed25519_program, 
34        msg
35    };
36}
37
38// Import the prelude (applies to both configurations)
39use prelude::*;
40
41// Additional imports specific to non-anchor builds
42#[cfg(not(feature = "anchor"))]
43use solana_program::{instruction::AccountMeta, pubkey::Pubkey};
44
45// Common imports
46use borsh::BorshDeserialize;
47use solana_compact_u16::CompactU16;
48use solana_ed25519_instruction::Ed25519Signature;
49use std::{collections::BTreeSet, io::Read};
50
51
52#[derive(Clone, Debug)]
53pub struct SignedTransaction {
54    pub header: TransactionHeader,
55    pub recent_blockhash: [u8; 32],
56    pub instructions: Vec<SignedInstruction>,
57}
58
59#[derive(Clone, BorshDeserialize, Debug)]
60pub struct TransactionHeader {
61    pub signers: u8,
62    pub readonly_signers: u8,
63    pub readonly: u8,
64}
65
66#[derive(Clone, Debug)]
67pub struct SignedInstruction {
68    pub program_id: Pubkey,
69    pub accounts: Vec<AccountMeta>,
70    pub data: Vec<u8>,
71}
72
73impl SignedTransaction {
74    pub fn from_bytes(data: &[u8]) -> std::io::Result<Self> {
75        let mut input = data;
76
77        // Deserialize the Ed25519 signature header and offsets
78        let offsets = Ed25519Signature::deserialize(&mut input)?.0;
79
80        let first_offset = offsets.first().ok_or(std::io::Error::new(
81            std::io::ErrorKind::InvalidData,
82            "Invalid number of instructions",
83        ))?;
84
85        // Use a BTreeSet to collect unique signers
86        let signers: BTreeSet<&Pubkey> =
87            BTreeSet::from_iter(offsets.iter().map(|o| o.get_signer(data)));
88
89        for offset in &offsets {
90            // Ensure all instruction indices are set to u16::MAX (0xffff)
91            if offset.signature_instruction_index != u16::MAX
92                || offset.public_key_instruction_index != u16::MAX
93                || offset.message_instruction_index != u16::MAX
94            {
95                return Err(std::io::Error::new(
96                    std::io::ErrorKind::InvalidData,
97                    "Instruction indices must be set to u16::MAX (0xffff)",
98                ));
99            }
100
101            // Ensure all signature offsets refer to the same message offset and size
102            if offset.message_data_offset != first_offset.message_data_offset
103                || offset.message_data_size != first_offset.message_data_size
104            {
105                return Err(std::io::Error::new(
106                    std::io::ErrorKind::InvalidData,
107                    "All signature offsets must refer to the same message data offset and size",
108                ));
109            }
110        }
111
112        // Extract the signed data based on the message_data_offset and message_data_size
113        let signed_data = data[first_offset.message_data_offset as usize
114            ..(first_offset.message_data_offset + first_offset.message_data_size) as usize]
115            .to_vec();
116
117        // Use the signed data for further deserialization
118        let mut input = &signed_data[..];
119
120        // Deserialize the transaction header
121        let header = TransactionHeader::deserialize(&mut input)?;
122
123        // Deserialize the number of account keys
124        let num_keys = CompactU16::deserialize(&mut input)?.0 as usize;
125
126        let mut account_flags = [
127            (header.signers - header.readonly_signers) as usize,
128            header.readonly_signers as usize,
129            num_keys - header.readonly as usize - header.signers as usize,
130            header.readonly as usize,
131        ];
132
133        if num_keys != account_flags.iter().sum::<usize>() {
134            return Err(std::io::Error::new(
135                std::io::ErrorKind::InvalidData,
136                "Invalid number of accounts",
137            ));
138        }
139        let mut tx_accounts = Vec::with_capacity(num_keys);
140
141        for _ in 0..num_keys {
142            let pubkey: Pubkey = Pubkey::deserialize(&mut input)?;
143
144            let (is_signer, is_writable) = if account_flags[0] > 0 {
145                if !signers.contains(&pubkey) {
146                    return Err(std::io::Error::new(
147                        std::io::ErrorKind::InvalidData,
148                        "Missing signer",
149                    ));
150                }
151                account_flags[0] -= 1;
152                (true, true)
153            } else if account_flags[1] > 0 {
154                if !signers.contains(&pubkey) {
155                    return Err(std::io::Error::new(
156                        std::io::ErrorKind::InvalidData,
157                        "Missing signer",
158                    ));
159                }
160                account_flags[1] -= 1;
161                (true, false)
162            } else if account_flags[2] > 0 {
163                account_flags[2] -= 1;
164                (false, true)
165            } else if account_flags[3] > 0 {
166                account_flags[3] -= 1;
167                (false, false)
168            } else {
169                return Err(std::io::Error::new(
170                    std::io::ErrorKind::InvalidData,
171                    "Invalid number of accounts",
172                ));
173            };
174
175            // Deserialize pubkey directly from input slice
176            tx_accounts.push(AccountMeta {
177                pubkey,
178                is_signer,
179                is_writable,
180            });
181        }
182
183        // Deserialize the recent blockhash
184        let recent_blockhash = <[u8; 32]>::deserialize(&mut input)?;
185
186        // Deserialize the number of instructions
187        let num_instructions = CompactU16::deserialize(&mut input)?.0 as usize;
188        let mut instructions: Vec<SignedInstruction> = Vec::with_capacity(num_instructions);
189
190        // Deserialize each instruction
191        for _ in 0..num_instructions {
192            // Get the program ID
193            let program_id_index = u8::deserialize(&mut input)? as usize;
194            let program_id = tx_accounts
195                .get(program_id_index)
196                .ok_or(std::io::Error::new(
197                    std::io::ErrorKind::InvalidData,
198                    "Invalid account index",
199                ))?
200                .pubkey;
201
202            // Get the number of accounts for this instruction
203            let ix_num_accounts = CompactU16::deserialize(&mut input)?.0 as usize;
204            let mut accounts = Vec::with_capacity(ix_num_accounts);
205
206            // Deserialize each account index for the instruction
207            for _ in 0..ix_num_accounts {
208                let account_index = u8::deserialize(&mut input)?;
209                let account = tx_accounts
210                    .get(account_index as usize)
211                    .ok_or(std::io::Error::new(
212                        std::io::ErrorKind::InvalidData,
213                        "Invalid account index",
214                    ))?
215                    .clone();
216                accounts.push(account);
217            }
218
219            // Get the instruction data
220            let ix_data_length = u8::deserialize(&mut input)? as usize;
221            let mut instruction_data = vec![0u8; ix_data_length];
222            input.read_exact(&mut instruction_data)?;
223
224            // Create the instruction
225            let instruction = SignedInstruction {
226                program_id,
227                accounts,
228                data: instruction_data,
229            };
230            instructions.push(instruction);
231        }
232
233        Ok(Self {
234            header,
235            recent_blockhash,
236            instructions,
237        })
238    }
239
240    pub fn try_deserialize_transaction(data: &mut &[u8]) -> core::result::Result<Self, SanitizeError> {    
241        // Get the current transaction index
242        let mut current = (*data).len() - 2;
243        let index = read_u16(&mut current, data)? + 1;
244
245        // Reset current to 0 to get the number of IXs
246        current = 0;
247        let num_instructions = read_u16(&mut current, data)?;
248    
249        // Make sure index is within number of instructions
250        if index >= num_instructions {
251            msg!("Index {} is out of bounds", index);
252            return Err(SanitizeError::IndexOutOfBounds);
253        }
254    
255        // index into the instruction byte-offset table.
256        current += index as usize * 2;
257        let start = read_u16(&mut current, data)?;
258    
259        current = start as usize;
260        let num_accounts = read_u16(&mut current, data)?;
261        if num_accounts != 0 {
262            msg!("Incorrect number of accounts");
263            return Err(SanitizeError::InvalidValue);
264        }
265
266        let program_id = read_pubkey(&mut current, data)?;
267        if program_id.ne(&ed25519_program::ID) {
268            msg!("Incorrect Program ID: {}", program_id.to_string());
269            return Err(SanitizeError::InvalidValue);
270        }
271        let data_len = read_u16(&mut current, data)?;
272
273        let data = read_slice(&mut current, data, data_len as usize)?;
274        Ok(SignedTransaction::from_bytes(&data).map_err(|_| SanitizeError::InvalidValue)?)
275    }
276}
277
278#[cfg(test)]
279mod tests {
280    use super::*;
281
282    #[cfg(feature = "anchor")]
283    use anchor_lang::pubkey;
284    #[cfg(not(feature = "anchor"))]
285    use solana_program::pubkey;
286
287    #[test]
288    fn deserialize() {
289        let sigix = [
290            0x01, 0x00, 0x10, 0x00, 0xff, 0xff, 0x54, 0x00, 0xff, 0xff, 0x50, 0x00, 0x7b, 0x01,
291            0xff, 0xff, 0x94, 0x9b, 0x2c, 0x98, 0xe6, 0x4f, 0xb0, 0x96, 0x1a, 0x44, 0x00, 0xc9,
292            0x5d, 0xd6, 0xfb, 0xe9, 0x91, 0xae, 0x7c, 0x7a, 0x12, 0xc5, 0x67, 0x09, 0x86, 0x31,
293            0x6f, 0x35, 0x23, 0x4d, 0x3d, 0x82, 0xc5, 0x7d, 0x7f, 0xaa, 0x98, 0xfd, 0xf3, 0xdc,
294            0x7b, 0xab, 0xa2, 0xd6, 0x0a, 0xf0, 0xe8, 0x97, 0x7c, 0x5b, 0xbe, 0x98, 0x03, 0x7e,
295            0x38, 0xae, 0xa7, 0x35, 0x9f, 0x87, 0xbc, 0xba, 0x20, 0x0f, 0x01, 0x00, 0x05, 0x09,
296            0xd2, 0x04, 0xc6, 0xd9, 0x47, 0x57, 0x21, 0xd4, 0xe2, 0x97, 0x1b, 0x56, 0x68, 0x1a,
297            0x28, 0x2b, 0x24, 0xad, 0x37, 0x81, 0xbf, 0x7f, 0xfd, 0x33, 0xe5, 0x19, 0x10, 0x7d,
298            0x13, 0xef, 0xa1, 0x9c, 0x12, 0x64, 0xd6, 0x2c, 0x9b, 0x73, 0xe8, 0xc9, 0x24, 0xd8,
299            0x37, 0xd8, 0x36, 0x21, 0x63, 0xad, 0x20, 0x9e, 0x38, 0x56, 0x14, 0x80, 0x47, 0x7d,
300            0xdc, 0x5f, 0x95, 0x86, 0x9c, 0x76, 0x21, 0x60, 0xcc, 0x01, 0x21, 0xa2, 0xb0, 0x81,
301            0xea, 0xe8, 0x30, 0x4b, 0x15, 0xc4, 0x2f, 0x4d, 0x69, 0x58, 0x7f, 0xf0, 0x26, 0x63,
302            0xc7, 0x6c, 0xa8, 0xb8, 0xc4, 0xb4, 0x47, 0x8f, 0xad, 0x82, 0xd8, 0x06, 0xf1, 0x80,
303            0x66, 0x8f, 0xed, 0xe9, 0x52, 0x84, 0x57, 0x4c, 0xc6, 0xf8, 0xa6, 0x76, 0x4c, 0x51,
304            0x17, 0x1f, 0x34, 0x3d, 0xc7, 0x4f, 0x3f, 0xdc, 0x20, 0x0f, 0x69, 0x65, 0x81, 0x78,
305            0x5f, 0xed, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
306            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
307            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x06, 0x46, 0x6f, 0xe5, 0x21, 0x17, 0x32,
308            0xff, 0xec, 0xad, 0xba, 0x72, 0xc3, 0x9b, 0xe7, 0xbc, 0x8c, 0xe5, 0xbb, 0xc5, 0xf7,
309            0x12, 0x6b, 0x2c, 0x43, 0x9b, 0x3a, 0x40, 0x00, 0x00, 0x00, 0xeb, 0x24, 0x67, 0xa8,
310            0x33, 0xcc, 0x03, 0xb9, 0xcd, 0xa9, 0xa8, 0x59, 0x8d, 0xe5, 0x0c, 0xfa, 0x32, 0xda,
311            0x7e, 0x97, 0x3a, 0x12, 0xb1, 0xff, 0xe5, 0x97, 0x78, 0x6c, 0xc0, 0x29, 0xae, 0x16,
312            0x06, 0xa7, 0xd5, 0x17, 0x19, 0x2c, 0x56, 0x8e, 0xe0, 0x8a, 0x84, 0x5f, 0x73, 0xd2,
313            0x97, 0x88, 0xcf, 0x03, 0x5c, 0x31, 0x45, 0xb2, 0x1a, 0xb3, 0x44, 0xd8, 0x06, 0x2e,
314            0xa9, 0x40, 0x00, 0x00, 0x06, 0xa7, 0xd5, 0x17, 0x19, 0x2c, 0x5c, 0x51, 0x21, 0x8c,
315            0xc9, 0x4c, 0x3d, 0x4a, 0xf1, 0x7f, 0x58, 0xda, 0xee, 0x08, 0x9b, 0xa1, 0xfd, 0x44,
316            0xe3, 0xdb, 0xd9, 0x8a, 0x00, 0x00, 0x00, 0x00, 0xc6, 0x58, 0x02, 0x06, 0x95, 0x3c,
317            0xb3, 0xaa, 0x4d, 0x8d, 0x4b, 0xd6, 0xd4, 0x28, 0xa3, 0xe8, 0xe3, 0xc2, 0xcc, 0x6f,
318            0x26, 0xa2, 0xa9, 0x15, 0xfe, 0xcb, 0xcc, 0x1f, 0xf0, 0xf1, 0x1b, 0x49, 0x03, 0x05,
319            0x00, 0x09, 0x03, 0xa0, 0x86, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05,
320            0x02, 0x40, 0x42, 0x0f, 0x00, 0x06, 0x07, 0x00, 0x02, 0x03, 0x01, 0x04, 0x07, 0x08,
321            0x18, 0xf6, 0x96, 0xec, 0xce, 0x6c, 0x3f, 0x3a, 0x0a, 0x64, 0x00, 0x00, 0x00, 0x00,
322            0x00, 0x00, 0x00, 0x40, 0x42, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00,
323        ];
324
325        let transaction = SignedTransaction::from_bytes(&sigix).unwrap();
326
327        assert_eq!(transaction.instructions.len(), 3);
328        assert_eq!(
329            transaction.instructions[0].program_id,
330            pubkey!("ComputeBudget111111111111111111111111111111")
331        );
332        assert!(transaction.instructions[0].accounts.is_empty());
333        assert_eq!(
334            transaction.instructions[1].program_id,
335            pubkey!("ComputeBudget111111111111111111111111111111")
336        );
337        assert!(transaction.instructions[1].accounts.is_empty());
338        assert_eq!(
339            transaction.instructions[2].accounts[0].pubkey,
340            pubkey!("F8pqnWWBZKyTAZgxNNRGLVCkBqf6pbJvvPY38trMr7cF")
341        );
342        assert!(transaction.instructions[2].accounts[0].is_signer);
343        assert_eq!(
344            transaction.instructions[2].program_id,
345            pubkey!("Gpu1L3Z6tHE6o1ksaBTASLNB4oUkoQe2qzQHqenK8bWd")
346        );
347        assert_eq!(transaction.instructions[2].accounts.len(), 7);
348    }
349}