1use crate::Result;
2use crate::crypto::sign_message;
3use crate::error::SolanaError;
4use crate::instructions::program_ids::COMPUTE_BUDGET_PROGRAM_ID;
5use crate::types::{
6 CompiledInstruction, Instruction, LegacyMessage, MAX_TRANSACTION_SIZE, Message,
7 MessageAddressTableLookup, Pubkey, SignatureBytes, VersionedMessage, VersionedMessageV0,
8};
9use borsh::{BorshDeserialize, BorshSerialize};
10use serde::{Deserialize, Serialize};
11
12#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
14pub struct Transaction {
15 pub signatures: Vec<SignatureBytes>,
17 pub message: Message,
19}
20
21impl Transaction {
22 pub fn new(message: Message) -> Self {
24 Self {
25 signatures: Vec::new(),
26 message,
27 }
28 }
29
30 pub fn add_signature(&mut self, signature: SignatureBytes) {
32 self.signatures.push(signature);
33 }
34
35 pub fn num_required_signatures(&self) -> u8 {
37 self.message.num_required_signatures()
38 }
39
40 pub fn num_readonly_signed_accounts(&self) -> u8 {
42 self.message.num_readonly_signed_accounts()
43 }
44
45 pub fn num_readonly_unsigned_accounts(&self) -> u8 {
47 self.message.num_readonly_unsigned_accounts()
48 }
49
50 pub fn account_keys(&self) -> &[Pubkey] {
52 &self.message.account_keys
53 }
54
55 pub fn recent_blockhash(&self) -> &[u8; 32] {
57 &self.message.recent_blockhash
58 }
59
60 pub fn instructions(&self) -> &[CompiledInstruction] {
62 &self.message.instructions
63 }
64
65 pub fn deserialize_with_version(bytes: &[u8]) -> Result<Self> {
67 if bytes.is_empty() {
68 return Err(SolanaError::DeserializationError(
69 "Empty transaction data".to_string(),
70 ));
71 }
72
73 let (num_signatures, len_bytes_consumed) = crate::decode_compact_u16_len(bytes)
75 .map_err(|e| SolanaError::DeserializationError(e.to_string()))?;
76
77 if bytes.len() < len_bytes_consumed + (num_signatures * 64) {
79 return Err(SolanaError::DeserializationError(
80 "Not enough bytes for signatures".to_string(),
81 ));
82 }
83
84 let mut signatures = Vec::with_capacity(num_signatures);
86 let mut offset = len_bytes_consumed; for _ in 0..num_signatures {
89 if offset + 64 > bytes.len() {
90 return Err(SolanaError::DeserializationError(
91 "Invalid signature data".to_string(),
92 ));
93 }
94
95 let sig_bytes: [u8; 64] = bytes[offset..offset + 64].try_into().map_err(|_| {
96 SolanaError::DeserializationError("Failed to convert signature bytes".to_string())
97 })?;
98
99 signatures.push(SignatureBytes::new(sig_bytes));
100 offset += 64;
101 }
102
103 let message_bytes = &bytes[offset..];
105
106 match manual_decode::decode_legacy_message(message_bytes, Vec::new()) {
108 Ok(VersionedTransaction::Legacy { message, .. }) => {
109 let regular_message = Message {
111 header: message.header,
112 account_keys: message.account_keys,
113 recent_blockhash: message.recent_blockhash,
114 instructions: message.instructions,
115 };
116
117 Ok(Self {
118 signatures,
119 message: regular_message,
120 })
121 }
122 _ => Err(SolanaError::DeserializationError(
123 "Failed to decode legacy message for Transaction".to_string(),
124 )),
125 }
126 }
127
128 pub fn serialize_legacy(&self) -> Result<Vec<u8>> {
130 let mut tx_wire_bytes: Vec<u8> = Vec::new();
131
132 let sig_len_bytes = crate::encode_length_to_compact_u16_bytes(self.signatures.len())?;
134 tx_wire_bytes.extend_from_slice(&sig_len_bytes);
135
136 for sig_bytes_wrapper in &self.signatures {
138 tx_wire_bytes.extend_from_slice(sig_bytes_wrapper.as_bytes());
139 }
140
141 let serialized_message = self
144 .message
145 .serialize_for_signing()
146 .map_err(SolanaError::SerializationError)?;
147 tx_wire_bytes.extend_from_slice(&serialized_message);
148
149 Ok(tx_wire_bytes)
150 }
151
152 pub fn sign(&mut self, private_keys: &[&[u8]]) -> Result<()> {
155 let message_bytes = self
157 .message
158 .serialize_for_signing()
159 .map_err(SolanaError::SerializationError)?;
160
161 self.signatures.clear();
163
164 let num_required_sigs = self.message.header.num_required_signatures as usize;
166
167 if private_keys.len() < num_required_sigs {
169 return Err(SolanaError::InvalidSignature(format!(
170 "insufficient private keys: {}, required: {}",
171 private_keys.len(),
172 num_required_sigs
173 )));
174 }
175
176 for private_key in private_keys.iter().take(num_required_sigs) {
178 let signature = sign_message(private_key, &message_bytes)?;
179 self.signatures.push(signature);
180 }
181
182 Ok(())
183 }
184
185 pub fn partial_sign(&mut self, private_keys: &[&[u8]], public_keys: &[Pubkey]) -> Result<()> {
188 if private_keys.len() != public_keys.len() {
189 return Err(SolanaError::InvalidSignature(format!(
190 "private keys count ({}) does not match public keys count ({})",
191 private_keys.len(),
192 public_keys.len()
193 )));
194 }
195
196 let message_bytes = self
198 .message
199 .serialize_for_signing()
200 .map_err(SolanaError::SerializationError)?;
201
202 let num_required_sigs = self.message.header.num_required_signatures as usize;
204 if self.signatures.len() < num_required_sigs {
205 self.signatures
206 .resize(num_required_sigs, SignatureBytes::new([0u8; 64]));
207 }
208
209 for (private_key, public_key) in private_keys.iter().zip(public_keys.iter()) {
211 if let Some(index) = self
213 .message
214 .account_keys
215 .iter()
216 .position(|k| k == public_key)
217 && index < num_required_sigs
218 {
219 let signature = sign_message(private_key, &message_bytes)?;
220 self.signatures[index] = signature;
221 }
222 }
223
224 Ok(())
225 }
226
227 pub fn is_signed(&self) -> bool {
229 let num_required = self.message.header.num_required_signatures as usize;
230 if self.signatures.len() < num_required {
231 return false;
232 }
233
234 for i in 0..num_required {
236 if self.signatures[i].as_bytes().iter().all(|&b| b == 0) {
237 return false;
238 }
239 }
240
241 true
242 }
243
244 pub fn validate_size(&self) -> Result<()> {
246 let serialized = self.serialize_legacy()?;
247
248 if serialized.len() > MAX_TRANSACTION_SIZE {
249 return Err(SolanaError::SerializationError(format!(
250 "Transaction size {} exceeds maximum of {} bytes",
251 serialized.len(),
252 MAX_TRANSACTION_SIZE
253 )));
254 }
255
256 Ok(())
257 }
258}
259
260#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
262pub enum VersionedTransaction {
263 Legacy {
265 signatures: Vec<SignatureBytes>,
267 message: LegacyMessage,
269 },
270 V0 {
272 signatures: Vec<SignatureBytes>,
274 message: VersionedMessageV0,
276 },
277}
278
279impl VersionedTransaction {
280 pub fn new(message: VersionedMessage) -> Self {
282 match message {
283 VersionedMessage::Legacy(msg) => Self::Legacy {
284 signatures: Vec::new(),
285 message: msg,
286 },
287 VersionedMessage::V0(msg) => Self::V0 {
288 signatures: Vec::new(),
289 message: msg,
290 },
291 }
292 }
293
294 pub fn add_signature(&mut self, signature: SignatureBytes) {
296 match self {
297 Self::Legacy { signatures, .. } => signatures.push(signature),
298 Self::V0 { signatures, .. } => signatures.push(signature),
299 }
300 }
301
302 pub fn num_required_signatures(&self) -> u8 {
304 match self {
305 Self::Legacy { message, .. } => message.header.num_required_signatures,
306 Self::V0 { message, .. } => message.header.num_required_signatures,
307 }
308 }
309
310 pub fn num_readonly_signed_accounts(&self) -> u8 {
312 match self {
313 Self::Legacy { message, .. } => message.header.num_readonly_signed_accounts,
314 Self::V0 { message, .. } => message.header.num_readonly_signed_accounts,
315 }
316 }
317
318 pub fn num_readonly_unsigned_accounts(&self) -> u8 {
320 match self {
321 Self::Legacy { message, .. } => message.header.num_readonly_unsigned_accounts,
322 Self::V0 { message, .. } => message.header.num_readonly_unsigned_accounts,
323 }
324 }
325
326 pub fn account_keys(&self) -> &[Pubkey] {
328 match self {
329 Self::Legacy { message, .. } => &message.account_keys,
330 Self::V0 { message, .. } => &message.account_keys,
331 }
332 }
333
334 pub fn recent_blockhash(&self) -> &[u8; 32] {
336 match self {
337 Self::Legacy { message, .. } => &message.recent_blockhash,
338 Self::V0 { message, .. } => &message.recent_blockhash,
339 }
340 }
341
342 pub fn instructions(&self) -> &[CompiledInstruction] {
344 match self {
345 Self::Legacy { message, .. } => &message.instructions,
346 Self::V0 { message, .. } => &message.instructions,
347 }
348 }
349
350 pub fn signatures(&self) -> &[SignatureBytes] {
351 match self {
352 Self::Legacy { signatures, .. } => signatures,
353 Self::V0 { signatures, .. } => signatures,
354 }
355 }
356
357 pub fn signatures_mut(&mut self) -> &mut Vec<SignatureBytes> {
358 match self {
359 Self::Legacy { signatures, .. } => signatures,
360 Self::V0 { signatures, .. } => signatures,
361 }
362 }
363
364 pub fn instructions_mut(&mut self) -> &mut Vec<CompiledInstruction> {
365 match self {
366 Self::Legacy { message, .. } => &mut message.instructions,
367 Self::V0 { message, .. } => &mut message.instructions,
368 }
369 }
370
371 fn compute_budget_program_index(&self) -> Option<u8> {
372 let cb_pubkey = Pubkey::from_base58(COMPUTE_BUDGET_PROGRAM_ID).ok()?;
373 self.account_keys()
374 .iter()
375 .position(|k| *k == cb_pubkey)
376 .map(|i| i as u8)
377 }
378
379 pub fn get_compute_unit_price(&self) -> Option<u64> {
380 let idx = self.compute_budget_program_index()?;
381 for ix in self.instructions() {
382 if ix.program_id_index == idx && ix.data.len() == 9 && ix.data[0] == 3 {
383 return Some(u64::from_le_bytes(ix.data[1..9].try_into().ok()?));
384 }
385 }
386 None
387 }
388
389 pub fn set_compute_unit_price(&mut self, micro_lamports: u64) -> Result<bool> {
390 if let Some(idx) = self.compute_budget_program_index() {
391 for ix in self.instructions_mut() {
392 if ix.program_id_index == idx && ix.data.len() == 9 && ix.data[0] == 3 {
393 ix.data[1..9].copy_from_slice(µ_lamports.to_le_bytes());
394 return Ok(true);
395 }
396 }
397 }
398 Ok(false)
399 }
400
401 pub fn get_compute_unit_limit(&self) -> Option<u32> {
402 let idx = self.compute_budget_program_index()?;
403 for ix in self.instructions() {
404 if ix.program_id_index == idx && ix.data.len() == 5 && ix.data[0] == 2 {
405 return Some(u32::from_le_bytes(ix.data[1..5].try_into().ok()?));
406 }
407 }
408 None
409 }
410
411 pub fn set_compute_unit_limit(&mut self, units: u32) -> Result<bool> {
412 if let Some(idx) = self.compute_budget_program_index() {
413 for ix in self.instructions_mut() {
414 if ix.program_id_index == idx && ix.data.len() == 5 && ix.data[0] == 2 {
415 ix.data[1..5].copy_from_slice(&units.to_le_bytes());
416 return Ok(true);
417 }
418 }
419 }
420 Ok(false)
421 }
422
423 pub fn add_instruction(&mut self, instruction: Instruction) -> Result<()> {
424 let message = match self {
425 Self::Legacy { message, .. } => message,
426 _ => {
427 return Err(SolanaError::SerializationError(
428 "add_instruction only supported for legacy transactions".to_string(),
429 ));
430 }
431 };
432
433 let mut new_writable_non_signers: Vec<Pubkey> = Vec::new();
434 let mut new_readonly_non_signers: Vec<Pubkey> = Vec::new();
435
436 if !message.account_keys.contains(&instruction.program_id) {
437 new_readonly_non_signers.push(instruction.program_id);
438 }
439
440 for meta in &instruction.accounts {
441 if !message.account_keys.contains(&meta.pubkey)
442 && !new_writable_non_signers.contains(&meta.pubkey)
443 && !new_readonly_non_signers.contains(&meta.pubkey)
444 {
445 if meta.is_writable && !meta.is_signer {
446 new_writable_non_signers.push(meta.pubkey);
447 } else if !meta.is_signer {
448 new_readonly_non_signers.push(meta.pubkey);
449 }
450 }
451 }
452
453 let insert_pos = message
454 .account_keys
455 .len()
456 .checked_sub(message.header.num_readonly_unsigned_accounts as usize)
457 .ok_or(SolanaError::InvalidMessage)?;
458 for (i, pubkey) in new_writable_non_signers.iter().enumerate() {
459 message.account_keys.insert(insert_pos + i, *pubkey);
460 }
461
462 let num_inserted = new_writable_non_signers.len();
463 if num_inserted > 0 {
464 for ix in &mut message.instructions {
465 if (ix.program_id_index as usize) >= insert_pos {
466 ix.program_id_index += num_inserted as u8;
467 }
468 for acc in &mut ix.accounts {
469 if (*acc as usize) >= insert_pos {
470 *acc += num_inserted as u8;
471 }
472 }
473 }
474 }
475
476 for pubkey in &new_readonly_non_signers {
477 message.account_keys.push(*pubkey);
478 }
479 message.header.num_readonly_unsigned_accounts += new_readonly_non_signers.len() as u8;
480
481 let program_id_index = message
482 .account_keys
483 .iter()
484 .position(|k| *k == instruction.program_id)
485 .unwrap() as u8;
486 let accounts: Vec<u8> = instruction
487 .accounts
488 .iter()
489 .map(|meta| {
490 message
491 .account_keys
492 .iter()
493 .position(|k| *k == meta.pubkey)
494 .unwrap() as u8
495 })
496 .collect();
497
498 message.instructions.push(CompiledInstruction {
499 program_id_index,
500 accounts,
501 data: instruction.data,
502 });
503
504 Ok(())
505 }
506
507 pub fn serialize_message(&self) -> Result<Vec<u8>> {
508 match self {
509 Self::Legacy { message, .. } => message
510 .serialize_for_signing()
511 .map_err(SolanaError::SerializationError),
512 Self::V0 { message, .. } => message
513 .serialize_for_signing()
514 .map_err(SolanaError::SerializationError),
515 }
516 }
517
518 pub fn serialize(&self) -> Result<Vec<u8>> {
519 let mut bytes = Vec::new();
520 let signatures = self.signatures();
521 let sig_len = crate::encode_length_to_compact_u16_bytes(signatures.len())
522 .map_err(SolanaError::SerializationError)?;
523 bytes.extend_from_slice(&sig_len);
524 for sig in signatures {
525 bytes.extend_from_slice(sig.as_bytes());
526 }
527 let message_bytes = self.serialize_message()?;
528 bytes.extend_from_slice(&message_bytes);
529 Ok(bytes)
530 }
531
532 pub fn deserialize_with_version(bytes: &[u8]) -> Result<Self> {
534 if bytes.is_empty() {
535 return Err(SolanaError::DeserializationError(
536 "Empty transaction data".to_string(),
537 ));
538 }
539
540 let (num_signatures, len_bytes_consumed) = crate::decode_compact_u16_len(bytes)
542 .map_err(|e| SolanaError::DeserializationError(e.to_string()))?;
543
544 if bytes.len() < len_bytes_consumed + (num_signatures * 64) {
546 return Err(SolanaError::DeserializationError(
547 "Not enough bytes for signatures".to_string(),
548 ));
549 }
550
551 let mut signatures = Vec::with_capacity(num_signatures);
553 let mut offset = len_bytes_consumed; for _ in 0..num_signatures {
556 if offset + 64 > bytes.len() {
557 return Err(SolanaError::DeserializationError(
558 "Invalid signature data".to_string(),
559 ));
560 }
561
562 let sig_bytes: [u8; 64] = bytes[offset..offset + 64].try_into().map_err(|_| {
563 SolanaError::DeserializationError("Failed to convert signature bytes".to_string())
564 })?;
565
566 signatures.push(SignatureBytes::new(sig_bytes));
567 offset += 64;
568 }
569
570 let message_bytes = &bytes[offset..];
572
573 self::manual_decode::decode_message(message_bytes, signatures)
575 }
576}
577
578mod manual_decode {
580 use super::*;
581 use crate::types::MessageHeader;
582
583 fn validate_header_counts(header: &MessageHeader, account_keys_len: usize) -> Result<()> {
585 let num_required_signatures = header.num_required_signatures as usize;
586 if num_required_signatures > account_keys_len {
587 return Err(SolanaError::DeserializationError(
588 "Message header num_required_signatures exceeds account_keys length".to_string(),
589 ));
590 }
591 if header.num_readonly_signed_accounts as usize > num_required_signatures {
592 return Err(SolanaError::DeserializationError(
593 "Message header num_readonly_signed_accounts exceeds num_required_signatures"
594 .to_string(),
595 ));
596 }
597 if header.num_readonly_unsigned_accounts as usize
598 > account_keys_len - num_required_signatures
599 {
600 return Err(SolanaError::DeserializationError(
601 "Message header num_readonly_unsigned_accounts exceeds the number of unsigned accounts"
602 .to_string(),
603 ));
604 }
605 Ok(())
606 }
607
608 pub fn decode_message(
619 bytes: &[u8],
620 signatures: Vec<SignatureBytes>,
621 ) -> Result<VersionedTransaction> {
622 if bytes.len() < 3 {
623 return Err(SolanaError::DeserializationError(
624 "Message bytes too short, need at least 3 bytes for header".to_string(),
625 ));
626 }
627
628 let is_versioned = (bytes[0] & 0x80) != 0;
630
631 if is_versioned {
632 let version = bytes[0] & 0x7F;
634
635 if version == 0 {
637 decode_v0_message(&bytes[1..], signatures)
638 } else {
639 Err(SolanaError::DeserializationError(format!(
640 "Unsupported message version: {version}"
641 )))
642 }
643 } else {
644 decode_legacy_message(bytes, signatures)
646 }
647 }
648
649 pub fn decode_legacy_message(
663 bytes: &[u8],
664 signatures: Vec<SignatureBytes>,
665 ) -> Result<VersionedTransaction> {
666 if bytes.len() < 3 {
667 return Err(SolanaError::DeserializationError(
668 "Legacy message too short".to_string(),
669 ));
670 }
671
672 let header = MessageHeader {
674 num_required_signatures: bytes[0],
675 num_readonly_signed_accounts: bytes[1],
676 num_readonly_unsigned_accounts: bytes[2],
677 };
678
679 let mut offset = 3;
680
681 if offset >= bytes.len() {
683 return Err(SolanaError::DeserializationError(
684 "Message too short: no account count".to_string(),
685 ));
686 }
687 let (account_count, len_bytes_consumed) =
688 crate::decode_compact_u16_len(&bytes[offset..]).map_err(SolanaError::from)?;
689 offset += len_bytes_consumed;
690
691 if offset + (account_count * 32) > bytes.len() {
692 return Err(SolanaError::DeserializationError(
693 "Message too short: not enough bytes for accounts".to_string(),
694 ));
695 }
696
697 let mut account_keys = Vec::with_capacity(account_count);
698 for _ in 0..account_count {
699 let mut key = [0u8; 32];
700 key.copy_from_slice(&bytes[offset..offset + 32]);
701 account_keys.push(Pubkey::new(key));
702 offset += 32;
703 }
704
705 validate_header_counts(&header, account_keys.len())?;
706
707 if offset + 32 > bytes.len() {
709 return Err(SolanaError::DeserializationError(
710 "Message too short: no recent blockhash".to_string(),
711 ));
712 }
713 let mut recent_blockhash = [0u8; 32];
714 recent_blockhash.copy_from_slice(&bytes[offset..offset + 32]);
715 offset += 32;
716
717 if offset >= bytes.len() {
719 return Err(SolanaError::DeserializationError(
720 "Message too short: no instruction count".to_string(),
721 ));
722 }
723 let (instruction_count, len_bytes_consumed) =
724 crate::decode_compact_u16_len(&bytes[offset..]).map_err(SolanaError::from)?;
725 offset += len_bytes_consumed;
726
727 let remaining = bytes.len().saturating_sub(offset);
729 if instruction_count.saturating_mul(3) > remaining {
730 return Err(SolanaError::DeserializationError(
731 "Message too short: instruction count exceeds remaining bytes".to_string(),
732 ));
733 }
734
735 let mut instructions = Vec::with_capacity(instruction_count);
736 for _ in 0..instruction_count {
737 if offset >= bytes.len() {
738 return Err(SolanaError::DeserializationError(
739 "Message too short: incomplete instruction".to_string(),
740 ));
741 }
742
743 let program_id_index = bytes[offset];
745 offset += 1;
746
747 if offset >= bytes.len() {
748 return Err(SolanaError::DeserializationError(
749 "Message too short: no account indices count".to_string(),
750 ));
751 }
752
753 let (account_indices_count, len_bytes_consumed) =
755 crate::decode_compact_u16_len(&bytes[offset..]).map_err(SolanaError::from)?;
756 offset += len_bytes_consumed;
757
758 if offset + account_indices_count > bytes.len() {
759 return Err(SolanaError::DeserializationError(
760 "Message too short: not enough account indices".to_string(),
761 ));
762 }
763
764 let accounts = bytes[offset..offset + account_indices_count].to_vec();
765 offset += account_indices_count;
766
767 if offset >= bytes.len() {
768 return Err(SolanaError::DeserializationError(
770 "Message too short: no instruction data length".to_string(),
771 ));
772 }
773
774 let (data_length, len_bytes_consumed) =
776 crate::decode_compact_u16_len(&bytes[offset..]).map_err(SolanaError::from)?;
777 offset += len_bytes_consumed;
778
779 if offset + data_length > bytes.len() {
780 return Err(SolanaError::DeserializationError(
781 "Message too short: not enough instruction data".to_string(),
782 ));
783 }
784
785 let data = bytes[offset..offset + data_length].to_vec();
786 offset += data_length;
787
788 instructions.push(CompiledInstruction {
789 program_id_index,
790 accounts,
791 data,
792 });
793 }
794
795 Ok(VersionedTransaction::Legacy {
796 signatures,
797 message: LegacyMessage {
798 header,
799 account_keys,
800 recent_blockhash,
801 instructions,
802 },
803 })
804 }
805
806 pub fn decode_v0_message(
809 bytes: &[u8],
810 signatures: Vec<SignatureBytes>,
811 ) -> Result<VersionedTransaction> {
812 if bytes.len() < 3 {
813 return Err(SolanaError::DeserializationError(
814 "V0 message too short".to_string(),
815 ));
816 }
817
818 let header = MessageHeader {
820 num_required_signatures: bytes[0],
821 num_readonly_signed_accounts: bytes[1],
822 num_readonly_unsigned_accounts: bytes[2],
823 };
824
825 let mut offset = 3;
826
827 if offset >= bytes.len() {
829 return Err(SolanaError::DeserializationError(
830 "Message too short: no account count".to_string(),
831 ));
832 }
833 let (account_count, len_bytes_consumed) =
834 crate::decode_compact_u16_len(&bytes[offset..]).map_err(SolanaError::from)?;
835 offset += len_bytes_consumed;
836
837 if offset + (account_count * 32) > bytes.len() {
838 return Err(SolanaError::DeserializationError(
839 "Message too short: not enough bytes for accounts".to_string(),
840 ));
841 }
842
843 let mut account_keys = Vec::with_capacity(account_count);
844 for _ in 0..account_count {
845 let mut key = [0u8; 32];
846 key.copy_from_slice(&bytes[offset..offset + 32]);
847 account_keys.push(Pubkey::new(key));
848 offset += 32;
849 }
850
851 validate_header_counts(&header, account_keys.len())?;
852
853 if offset + 32 > bytes.len() {
855 return Err(SolanaError::DeserializationError(
856 "Message too short: no recent blockhash".to_string(),
857 ));
858 }
859 let mut recent_blockhash = [0u8; 32];
860 recent_blockhash.copy_from_slice(&bytes[offset..offset + 32]);
861 offset += 32;
862
863 if offset >= bytes.len() {
865 return Err(SolanaError::DeserializationError(
866 "Message too short: no instruction count".to_string(),
867 ));
868 }
869 let (instruction_count, len_bytes_consumed) =
870 crate::decode_compact_u16_len(&bytes[offset..]).map_err(SolanaError::from)?;
871 offset += len_bytes_consumed;
872
873 let remaining = bytes.len().saturating_sub(offset);
875 if instruction_count.saturating_mul(3) > remaining {
876 return Err(SolanaError::DeserializationError(
877 "Message too short: instruction count exceeds remaining bytes".to_string(),
878 ));
879 }
880
881 let mut instructions = Vec::with_capacity(instruction_count);
882 for _ in 0..instruction_count {
883 if offset >= bytes.len() {
884 return Err(SolanaError::DeserializationError(
885 "Message too short: incomplete instruction".to_string(),
886 ));
887 }
888
889 let program_id_index = bytes[offset];
891 offset += 1;
892
893 if offset >= bytes.len() {
894 return Err(SolanaError::DeserializationError(
895 "Message too short: no account indices count".to_string(),
896 ));
897 }
898
899 let (account_indices_count, len_bytes_consumed) =
901 crate::decode_compact_u16_len(&bytes[offset..]).map_err(SolanaError::from)?;
902 offset += len_bytes_consumed;
903
904 if offset + account_indices_count > bytes.len() {
905 return Err(SolanaError::DeserializationError(
906 "Message too short: not enough account indices".to_string(),
907 ));
908 }
909
910 let accounts = bytes[offset..offset + account_indices_count].to_vec();
911 offset += account_indices_count;
912
913 if offset >= bytes.len() {
914 return Err(SolanaError::DeserializationError(
916 "Message too short: no instruction data length".to_string(),
917 ));
918 }
919
920 let (data_length, len_bytes_consumed) =
922 crate::decode_compact_u16_len(&bytes[offset..]).map_err(SolanaError::from)?;
923 offset += len_bytes_consumed;
924
925 if offset + data_length > bytes.len() {
926 return Err(SolanaError::DeserializationError(
927 "Message too short: not enough instruction data".to_string(),
928 ));
929 }
930
931 let data = bytes[offset..offset + data_length].to_vec();
932 offset += data_length;
933
934 instructions.push(CompiledInstruction {
935 program_id_index,
936 accounts,
937 data,
938 });
939 }
940
941 let mut address_table_lookups = Vec::new();
943
944 if offset < bytes.len() {
946 let (lookup_table_count, len_bytes_consumed) =
947 crate::decode_compact_u16_len(&bytes[offset..])
948 .map_err(|e| SolanaError::DeserializationError(e.to_string()))?;
949 offset += len_bytes_consumed;
950
951 for _ in 0..lookup_table_count {
952 if offset + 32 > bytes.len() {
953 return Err(SolanaError::DeserializationError(
954 "Message too short: incomplete address lookup table".to_string(),
955 ));
956 }
957
958 let mut key = [0u8; 32];
960 key.copy_from_slice(&bytes[offset..offset + 32]);
961 let lookup_table_key = Pubkey::new(key);
962 offset += 32;
963
964 if offset >= bytes.len() {
966 return Err(SolanaError::DeserializationError(
967 "Message too short: no writable indexes count".to_string(),
968 ));
969 }
970 let (writable_indexes_count, len_bytes_consumed) =
971 crate::decode_compact_u16_len(&bytes[offset..])
972 .map_err(|e| SolanaError::DeserializationError(e.to_string()))?;
973 offset += len_bytes_consumed;
974
975 if offset + writable_indexes_count > bytes.len() {
976 return Err(SolanaError::DeserializationError(
977 "Message too short: not enough writable indexes".to_string(),
978 ));
979 }
980
981 let writable_indexes = bytes[offset..offset + writable_indexes_count].to_vec();
982 offset += writable_indexes_count;
983
984 if offset >= bytes.len() {
986 return Err(SolanaError::DeserializationError(
987 "Message too short: no readonly indexes count".to_string(),
988 ));
989 }
990 let (readonly_indexes_count, len_bytes_consumed) =
991 crate::decode_compact_u16_len(&bytes[offset..])
992 .map_err(|e| SolanaError::DeserializationError(e.to_string()))?;
993 offset += len_bytes_consumed;
994
995 if offset + readonly_indexes_count > bytes.len() {
996 return Err(SolanaError::DeserializationError(
997 "Message too short: not enough readonly indexes".to_string(),
998 ));
999 }
1000
1001 let readonly_indexes = bytes[offset..offset + readonly_indexes_count].to_vec();
1002 offset += readonly_indexes_count;
1003
1004 address_table_lookups.push(MessageAddressTableLookup {
1005 account_key: lookup_table_key,
1006 writable_indexes,
1007 readonly_indexes,
1008 });
1009 }
1010 }
1011
1012 Ok(VersionedTransaction::V0 {
1013 signatures,
1014 message: VersionedMessageV0 {
1015 header,
1016 account_keys,
1017 recent_blockhash,
1018 instructions,
1019 address_table_lookups,
1020 },
1021 })
1022 }
1023}
1024
1025#[cfg(test)]
1026mod tests {
1027 use super::*;
1028 use crate::{
1029 instructions::system,
1030 types::{Pubkey, SignatureBytes},
1031 };
1032 use base64::{Engine, engine::general_purpose::STANDARD};
1033
1034 const LEGACY_TX: &str = "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAgWAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEbrtjJdvWJAv9GZTGL8LaZtMvDe4j2ery4z7rOkRbioxZflXLFqWqlAt1REFSiam0ljvfB1tbBruEpGRTcUQIyQ+ddH9NRneQZQXje5U/3c4cZ2f1JESi76CvBvRoQ6I1LeNzfZ4ZONkowCnqCyeo5+D6Q21gn3U7HVw/KD3HyUW5gVpu5F8ZojWkXLg/+3N6q3ojiaqYyBIbz7VP7jS5Yktrxv5b22C/EFSDs5jUPA7Gz3GLdBNs0iwBHlqUqNEeyNpDX0HWNHV2LiVDOx6m018ea6P+1xroNvWKhmDeTW7oqHXAEK1ih5IO68BBiiKqWNR5VZdBgBsnR+rZKfpfuyE3yQziYO+SoWzCXuvQLyVcRCNKJrACzaN8XXUR1z3rOt8T1lYUIIAQS7tqgcLRsn18N4vVQgXQyv3bQWjh3JtpQT3Bgy9N9myGC4PDjGuVnx2Y7mF4eqlysb0rgrdrB2+FMK6YBPXtlXF4QPTY6rEe+hxkBpCoGK7UJu5BHUK4gJhAewgMolkoyq6sTbFQFuR86447k9ky2veh5uGg40gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjJclj04kifG7PRApFI4NgwtaE5na/xCEBI572Nvp+FkDBkZv5SEXMv/srbpyw5vnvIzlu8X3EmssQ5s6QAAAAMb6evO+2606PWXzaqvJdDGxu+TC0vbg5HymAgNFL11hBUpTWpkpIQZNJOhxYNo4fHw1td28kruB5B+oQEEFRI0Gm4hX/quBhPtof2NGGMA12sQ53BrrO1WYoPAAAAAAAQbd9uHXZaGT2cvhRs7reawctIXtX1s3kTqM9YV+/wCpDgNoX46QkFPkWBIcZvWnau3HcGqhHIL4qpUqjyt4ealuCa42Moiy1mB8REcWJlkis4eCMyKfY2HMRfldn8r2XwcQAAUCoGgGABAACQNwEQEAAAAAAA8GAAYAEw4UAQAVERQUEgAHExEGCQoCBAULDAgBMSsE7QsayR5iC50OAAAAAAA8XqkAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEBAAAABgIUAwYAAAEJFAMKAwAJA8wSAAAAAAAADgIADQwCAAAAODEAAAAAAAA=";
1036
1037 const MAYAN_V0_TX: &str = "ATzYOiofQZSWsNe3SxxEPip+Xp9A2Fji+h0xfs7FkmvQxNNgwjeEbTlMr7+e42q9vcvExw2CX4PgNBRuY77O+waAAQAEDPlBHYJN7SVAqQdmNtdFQsCIDVJuEnf59VTtTCOGI7yLh4jpmImexNtJSORTO+sbJ63Aysdx88si41jIW1Wf65qHxwlVbaZ8xI24o/VzmleK1NqPB2lMTcy78ZFbqJ6agIqQAqWC7XmuIVDA/VxhSMZPxFOazPZMJbWyD+TYtXxA3sS/qzC61MydFxPOY3xt62Ug5Tp3r/hC0NimkXNfrMH0UmoX+WTY7c2jVeACjg8EqVgtZZSXgaQRvotGaelPhCySBd5s0S8tvrZZSGGBUknE3Jjh4aGsgXpNY0QHkFnJayU0QDsmAQ7sF/E5yI6Oq1k8w8tnKB6wJR28JzZwp3KVGAf9PgfpG6VoBYOYtT4QWhLzz8wJo5Da/9f9tVVfo7Qj5Z1paZLqq3kUJ1PAm9bYE1qpQE9jUkcSHEnSn0OVAwZGb+UhFzL/7K26csOb57yM5bvF9xJrLEObOkAAAAAGTCSuZOXkbU4/LKndRkF4gm16E7to0DdpTPoefoS0rYF08m4FFLws+yIpIkWYIyALDIz0sekCn1BgZGSqLNo5CwsAF0FkUEJ2ZE5kVGxlWmNsc25JeDVkeUExCgAFAhxCBwAKAAkDBBcBAAAAAAAJBxUABgUWGRQACQcVAAEAGxkUAQEJAxkAAQwCAAAAC/UHPQYAAAAJAhQBAREJBxUAAwAWGRQBAQgoHAABAxsWFBQGHRwhACITAQMPERIUBBACBxweAA4gHw0MAQMbFhQUGjIBLQAAALtk+swxxK8UC/UHPQYAAAD8nvqKAAAAAGQAAAAAAAIAAAAaQAYAAl8A0CAAAgkEFAEAAAEJCQoYAAAFBgMWFxQZxgEgTCkMJ6KE2yRjS4oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhOLnPgTBemY6Iqc117gEL72WnXMeAAAAAAAAAAAAAAAAAIM1ifzW7bbgj0x8MtT3G1S9oCkT/ySLiQAAAAAAAAAAAAAAAG4DAAAAAAAA8dcFAAAAAAA7a4ppAAAAAAAAAAAAAAAAAAAAAN3bmpXkQ6IE64ZQ1epXjtcH/iEjAAMC0SsrhG32PclzqA5blk8lqOrlLpR5OoOt60ksQpGLgw4D2cMN+5YRja4DNaX11bThHmwP8vCzdRYtSPXpFGWT2KsACgUGESAhKSowMTQme3jXiWKuyj4qkRn+CZK3WspZpXBM+tnHyaYm4WA/BAMICgsDBgkMIbxt+8RM8X78HZP9nB+0Ah2xfOX9io4UH0AdkLgPT00Fdnd6fH4CdHU=";
1039
1040 fn decode_legacy_tx() -> VersionedTransaction {
1041 let data = STANDARD.decode(LEGACY_TX).unwrap();
1042 VersionedTransaction::deserialize_with_version(&data).unwrap()
1043 }
1044
1045 fn decode_mayan_tx() -> VersionedTransaction {
1046 let data = STANDARD.decode(MAYAN_V0_TX).unwrap();
1047 VersionedTransaction::deserialize_with_version(&data).unwrap()
1048 }
1049
1050 #[test]
1051 fn decode_legacy() {
1052 let tx = decode_legacy_tx();
1053 assert!(matches!(tx, VersionedTransaction::Legacy { .. }));
1054 assert_eq!(tx.signatures().len(), 1);
1055 assert_eq!(tx.account_keys().len(), 22);
1056 assert_eq!(tx.instructions().len(), 7);
1057 }
1058
1059 #[test]
1060 fn decode_v0() {
1061 let tx = decode_mayan_tx();
1062 assert!(matches!(tx, VersionedTransaction::V0 { .. }));
1063 assert_eq!(tx.signatures().len(), 1);
1064 }
1065
1066 #[test]
1067 fn signatures_accessors() {
1068 let mut tx = decode_legacy_tx();
1069 let original_sig = tx.signatures()[0];
1070
1071 tx.signatures_mut()[0] = SignatureBytes::new([42; 64]);
1072 assert_eq!(tx.signatures()[0], SignatureBytes::new([42; 64]));
1073 assert_ne!(tx.signatures()[0], original_sig);
1074 }
1075
1076 #[test]
1077 fn get_compute_unit_price_from_legacy() {
1078 assert_eq!(decode_legacy_tx().get_compute_unit_price(), Some(70_000));
1079 }
1080
1081 #[test]
1082 fn get_compute_unit_price_from_v0() {
1083 assert_eq!(decode_mayan_tx().get_compute_unit_price(), Some(71_428));
1084 }
1085
1086 #[test]
1087 fn set_compute_unit_price_legacy() {
1088 let mut tx = decode_legacy_tx();
1089 assert!(tx.set_compute_unit_price(999_999).unwrap());
1090 assert_eq!(tx.get_compute_unit_price(), Some(999_999));
1091 }
1092
1093 #[test]
1094 fn get_compute_unit_limit_from_legacy() {
1095 assert_eq!(decode_legacy_tx().get_compute_unit_limit(), Some(420_000));
1096 }
1097
1098 #[test]
1099 fn get_compute_unit_limit_from_v0() {
1100 assert_eq!(decode_mayan_tx().get_compute_unit_limit(), Some(475_676));
1101 }
1102
1103 #[test]
1104 fn set_compute_unit_limit_legacy() {
1105 let mut tx = decode_legacy_tx();
1106 assert!(tx.set_compute_unit_limit(500_000).unwrap());
1107 assert_eq!(tx.get_compute_unit_limit(), Some(500_000));
1108 }
1109
1110 #[test]
1111 fn add_instruction_appends_to_legacy() {
1112 let mut tx = decode_legacy_tx();
1113 let initial_ix_count = tx.instructions().len();
1114 let price_before = tx.get_compute_unit_price().unwrap();
1115
1116 let from = tx.account_keys()[0];
1117 let to = Pubkey::new([99; 32]);
1118 tx.add_instruction(system::transfer(&from, &to, 5000))
1119 .unwrap();
1120
1121 assert_eq!(tx.instructions().len(), initial_ix_count + 1);
1122 assert!(tx.account_keys().contains(&to));
1123 assert_eq!(tx.get_compute_unit_price(), Some(price_before));
1124 }
1125
1126 #[test]
1127 fn add_instruction_errors_on_v0() {
1128 let mut tx = decode_mayan_tx();
1129 let from = tx.account_keys()[0];
1130 let to = Pubkey::new([2; 32]);
1131 assert!(
1132 tx.add_instruction(system::transfer(&from, &to, 100))
1133 .is_err()
1134 );
1135 }
1136
1137 fn legacy_message_prefix(header: [u8; 3], num_accounts: u8) -> Vec<u8> {
1139 let mut bytes = header.to_vec();
1140 bytes.push(num_accounts);
1141 bytes.extend(std::iter::repeat_n(0u8, 32 * num_accounts as usize));
1142 bytes.extend_from_slice(&[0u8; 32]);
1143 bytes
1144 }
1145
1146 #[test]
1147 fn decode_legacy_message_rejects_huge_instruction_count() {
1148 let mut bytes = legacy_message_prefix([1, 0, 0], 1);
1149 bytes.extend_from_slice(&crate::encode_length_to_compact_u16_bytes(60_000).unwrap());
1150
1151 let result = manual_decode::decode_legacy_message(&bytes, Vec::new());
1152 assert!(
1153 result.is_err(),
1154 "huge instruction count exceeding remaining bytes must be rejected"
1155 );
1156
1157 let mut tx_bytes = vec![0u8];
1159 tx_bytes.extend_from_slice(&bytes);
1160 assert!(VersionedTransaction::deserialize_with_version(&tx_bytes).is_err());
1161 }
1162
1163 #[test]
1164 fn decode_legacy_message_rejects_inconsistent_header() {
1165 let mut bytes = legacy_message_prefix([1, 0, 5], 1);
1167 bytes.push(0); let result = manual_decode::decode_legacy_message(&bytes, Vec::new());
1170 assert!(
1171 result.is_err(),
1172 "header counts inconsistent with account_keys length must be rejected at parse time"
1173 );
1174 }
1175
1176 #[test]
1177 fn decode_legacy_message_rejects_readonly_unsigned_exceeding_unsigned_section() {
1178 let mut bytes = legacy_message_prefix([1, 0, 2], 2);
1180 bytes.push(0); let result = manual_decode::decode_legacy_message(&bytes, Vec::new());
1183 assert!(
1184 result.is_err(),
1185 "num_readonly_unsigned_accounts exceeding the unsigned section must be rejected"
1186 );
1187 }
1188
1189 #[test]
1190 fn decode_legacy_message_rejects_readonly_signed_exceeding_required_signatures() {
1191 let mut bytes = legacy_message_prefix([1, 2, 0], 2);
1193 bytes.push(0); let result = manual_decode::decode_legacy_message(&bytes, Vec::new());
1196 assert!(
1197 result.is_err(),
1198 "num_readonly_signed_accounts exceeding num_required_signatures must be rejected"
1199 );
1200 }
1201
1202 #[test]
1203 fn decode_v0_message_rejects_readonly_unsigned_exceeding_unsigned_section() {
1204 let mut bytes = legacy_message_prefix([1, 0, 2], 2);
1206 bytes.push(0); bytes.push(0); let result = manual_decode::decode_v0_message(&bytes, Vec::new());
1210 assert!(
1211 result.is_err(),
1212 "num_readonly_unsigned_accounts exceeding the unsigned section must be rejected in V0 too"
1213 );
1214 }
1215
1216 #[test]
1217 fn serialize_roundtrip_legacy() {
1218 let data = STANDARD.decode(LEGACY_TX).unwrap();
1219 let tx = VersionedTransaction::deserialize_with_version(&data).unwrap();
1220
1221 let reserialized = tx.serialize().unwrap();
1222 assert_eq!(reserialized, data, "byte-exact roundtrip failed");
1223
1224 let tx2 = VersionedTransaction::deserialize_with_version(&reserialized).unwrap();
1225 assert!(matches!(tx2, VersionedTransaction::Legacy { .. }));
1226 assert_eq!(tx2.get_compute_unit_price(), Some(70_000));
1227 assert_eq!(tx2.get_compute_unit_limit(), Some(420_000));
1228 }
1229
1230 #[test]
1231 fn serialize_roundtrip_v0() {
1232 let data = STANDARD.decode(MAYAN_V0_TX).unwrap();
1233 let tx = VersionedTransaction::deserialize_with_version(&data).unwrap();
1234
1235 let reserialized = tx.serialize().unwrap();
1236 assert_eq!(reserialized, data, "byte-exact roundtrip failed");
1237
1238 let tx2 = VersionedTransaction::deserialize_with_version(&reserialized).unwrap();
1239 assert!(matches!(tx2, VersionedTransaction::V0 { .. }));
1240 assert_eq!(tx2.get_compute_unit_price(), Some(71_428));
1241 assert_eq!(tx2.get_compute_unit_limit(), Some(475_676));
1242 }
1243
1244 #[test]
1245 fn sign_and_roundtrip() {
1246 let mut tx = decode_legacy_tx();
1247 let private_key = [1u8; 32];
1248
1249 let message_bytes = tx.serialize_message().unwrap();
1250 let sig = sign_message(&private_key, &message_bytes).unwrap();
1251 tx.signatures_mut()[0] = sig;
1252
1253 let bytes = tx.serialize().unwrap();
1254 let deserialized = VersionedTransaction::deserialize_with_version(&bytes).unwrap();
1255 assert_eq!(deserialized.signatures()[0], sig);
1256 assert_ne!(deserialized.signatures()[0], SignatureBytes::default());
1257 }
1258}