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 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#[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
38use prelude::*;
40
41#[cfg(not(feature = "anchor"))]
43use solana_program::{instruction::AccountMeta, pubkey::Pubkey};
44
45use 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 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 let signers: BTreeSet<&Pubkey> =
87 BTreeSet::from_iter(offsets.iter().map(|o| o.get_signer(data)));
88
89 for offset in &offsets {
90 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 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 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 let mut input = &signed_data[..];
119
120 let header = TransactionHeader::deserialize(&mut input)?;
122
123 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 tx_accounts.push(AccountMeta {
177 pubkey,
178 is_signer,
179 is_writable,
180 });
181 }
182
183 let recent_blockhash = <[u8; 32]>::deserialize(&mut input)?;
185
186 let num_instructions = CompactU16::deserialize(&mut input)?.0 as usize;
188 let mut instructions: Vec<SignedInstruction> = Vec::with_capacity(num_instructions);
189
190 for _ in 0..num_instructions {
192 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 let ix_num_accounts = CompactU16::deserialize(&mut input)?.0 as usize;
204 let mut accounts = Vec::with_capacity(ix_num_accounts);
205
206 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 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 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 let mut current = (*data).len() - 2;
243 let index = read_u16(&mut current, data)? + 1;
244
245 current = 0;
247 let num_instructions = read_u16(&mut current, data)?;
248
249 if index >= num_instructions {
251 msg!("Index {} is out of bounds", index);
252 return Err(SanitizeError::IndexOutOfBounds);
253 }
254
255 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}