1use crate::{
13 address_lookup_table_account::AddressLookupTableAccount,
14 bpf_loader_upgradeable,
15 hash::Hash,
16 instruction::{CompiledInstruction, Instruction},
17 message::{
18 compiled_keys::{CompileError, CompiledKeys},
19 legacy::is_builtin_key_or_sysvar,
20 AccountKeys, MessageHeader, MESSAGE_VERSION_PREFIX,
21 },
22 pubkey::Pubkey,
23 sanitize::SanitizeError,
24 short_vec,
25};
26pub use loaded::*;
27
28mod loaded;
29
30#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, AbiExample)]
33#[serde(rename_all = "camelCase")]
34pub struct MessageAddressTableLookup {
35 pub account_key: Pubkey,
37 #[serde(with = "short_vec")]
39 pub writable_indexes: Vec<u8>,
40 #[serde(with = "short_vec")]
42 pub readonly_indexes: Vec<u8>,
43}
44
45#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, AbiExample)]
54#[serde(rename_all = "camelCase")]
55pub struct Message {
56 pub header: MessageHeader,
60
61 #[serde(with = "short_vec")]
63 pub account_keys: Vec<Pubkey>,
64
65 pub recent_blockhash: Hash,
67
68 #[serde(with = "short_vec")]
82 pub instructions: Vec<CompiledInstruction>,
83
84 #[serde(with = "short_vec")]
87 pub address_table_lookups: Vec<MessageAddressTableLookup>,
88}
89
90impl Message {
91 pub fn sanitize(&self, reject_dynamic_program_ids: bool) -> Result<(), SanitizeError> {
93 let num_static_account_keys = self.account_keys.len();
94 if usize::from(self.header.num_required_signatures)
95 .saturating_add(usize::from(self.header.num_readonly_unsigned_accounts))
96 > num_static_account_keys
97 {
98 return Err(SanitizeError::IndexOutOfBounds);
99 }
100
101 if self.header.num_readonly_signed_accounts >= self.header.num_required_signatures {
103 return Err(SanitizeError::InvalidValue);
104 }
105
106 let num_dynamic_account_keys = {
107 let mut total_lookup_keys: usize = 0;
108 for lookup in &self.address_table_lookups {
109 let num_lookup_indexes = lookup
110 .writable_indexes
111 .len()
112 .saturating_add(lookup.readonly_indexes.len());
113
114 if num_lookup_indexes == 0 {
116 return Err(SanitizeError::InvalidValue);
117 }
118
119 total_lookup_keys = total_lookup_keys.saturating_add(num_lookup_indexes);
120 }
121 total_lookup_keys
122 };
123
124 if num_static_account_keys == 0 {
128 return Err(SanitizeError::InvalidValue);
129 }
130
131 let total_account_keys = num_static_account_keys.saturating_add(num_dynamic_account_keys);
134 if total_account_keys > 256 {
135 return Err(SanitizeError::IndexOutOfBounds);
136 }
137
138 let max_account_ix = total_account_keys
141 .checked_sub(1)
142 .expect("message doesn't contain any account keys");
143
144 let max_program_id_ix = if reject_dynamic_program_ids {
148 num_static_account_keys
151 .checked_sub(1)
152 .expect("message doesn't contain any static account keys")
153 } else {
154 max_account_ix
155 };
156
157 for ci in &self.instructions {
158 if usize::from(ci.program_id_index) > max_program_id_ix {
159 return Err(SanitizeError::IndexOutOfBounds);
160 }
161 if ci.program_id_index == 0 {
163 return Err(SanitizeError::IndexOutOfBounds);
164 }
165 for ai in &ci.accounts {
166 if usize::from(*ai) > max_account_ix {
167 return Err(SanitizeError::IndexOutOfBounds);
168 }
169 }
170 }
171
172 Ok(())
173 }
174}
175
176impl Message {
177 pub fn try_compile(
257 payer: &Pubkey,
258 instructions: &[Instruction],
259 address_lookup_table_accounts: &[AddressLookupTableAccount],
260 recent_blockhash: Hash,
261 ) -> Result<Self, CompileError> {
262 let mut compiled_keys = CompiledKeys::compile(instructions, Some(*payer));
263
264 let mut address_table_lookups = Vec::with_capacity(address_lookup_table_accounts.len());
265 let mut loaded_addresses_list = Vec::with_capacity(address_lookup_table_accounts.len());
266 for lookup_table_account in address_lookup_table_accounts {
267 if let Some((lookup, loaded_addresses)) =
268 compiled_keys.try_extract_table_lookup(lookup_table_account)?
269 {
270 address_table_lookups.push(lookup);
271 loaded_addresses_list.push(loaded_addresses);
272 }
273 }
274
275 let (header, static_keys) = compiled_keys.try_into_message_components()?;
276 let dynamic_keys = loaded_addresses_list.into_iter().collect();
277 let account_keys = AccountKeys::new(&static_keys, Some(&dynamic_keys));
278 let instructions = account_keys.try_compile_instructions(instructions)?;
279
280 Ok(Self {
281 header,
282 account_keys: static_keys,
283 recent_blockhash,
284 instructions,
285 address_table_lookups,
286 })
287 }
288
289 pub fn serialize(&self) -> Vec<u8> {
291 bincode::serialize(&(MESSAGE_VERSION_PREFIX, self)).unwrap()
292 }
293
294 pub fn is_key_called_as_program(&self, key_index: usize) -> bool {
296 if let Ok(key_index) = u8::try_from(key_index) {
297 self.instructions
298 .iter()
299 .any(|ix| ix.program_id_index == key_index)
300 } else {
301 false
302 }
303 }
304
305 fn is_writable_index(&self, key_index: usize) -> bool {
308 let header = &self.header;
309 let num_account_keys = self.account_keys.len();
310 let num_signed_accounts = usize::from(header.num_required_signatures);
311 if key_index >= num_account_keys {
312 let loaded_addresses_index = key_index.saturating_sub(num_account_keys);
313 let num_writable_dynamic_addresses = self
314 .address_table_lookups
315 .iter()
316 .map(|lookup| lookup.writable_indexes.len())
317 .sum();
318 loaded_addresses_index < num_writable_dynamic_addresses
319 } else if key_index >= num_signed_accounts {
320 let num_unsigned_accounts = num_account_keys.saturating_sub(num_signed_accounts);
321 let num_writable_unsigned_accounts = num_unsigned_accounts
322 .saturating_sub(usize::from(header.num_readonly_unsigned_accounts));
323 let unsigned_account_index = key_index.saturating_sub(num_signed_accounts);
324 unsigned_account_index < num_writable_unsigned_accounts
325 } else {
326 let num_writable_signed_accounts = num_signed_accounts
327 .saturating_sub(usize::from(header.num_readonly_signed_accounts));
328 key_index < num_writable_signed_accounts
329 }
330 }
331
332 fn is_upgradeable_loader_in_static_keys(&self) -> bool {
334 self.account_keys
335 .iter()
336 .any(|&key| key == bpf_loader_upgradeable::id())
337 }
338
339 pub fn is_maybe_writable(&self, key_index: usize) -> bool {
343 self.is_writable_index(key_index)
344 && !{
345 self.account_keys
347 .get(key_index)
348 .map(is_builtin_key_or_sysvar)
349 .unwrap_or_default()
350 }
351 && !{
352 self.is_key_called_as_program(key_index)
354 && !self.is_upgradeable_loader_in_static_keys()
355 }
356 }
357}
358
359#[cfg(test)]
360mod tests {
361 use {
362 super::*,
363 crate::{instruction::AccountMeta, message::VersionedMessage},
364 };
365
366 #[test]
367 fn test_sanitize() {
368 assert!(Message {
369 header: MessageHeader {
370 num_required_signatures: 1,
371 ..MessageHeader::default()
372 },
373 account_keys: vec![Pubkey::new_unique()],
374 ..Message::default()
375 }
376 .sanitize(
377 true, )
379 .is_ok());
380 }
381
382 #[test]
383 fn test_sanitize_with_instruction() {
384 assert!(Message {
385 header: MessageHeader {
386 num_required_signatures: 1,
387 ..MessageHeader::default()
388 },
389 account_keys: vec![Pubkey::new_unique(), Pubkey::new_unique()],
390 instructions: vec![CompiledInstruction {
391 program_id_index: 1,
392 accounts: vec![0],
393 data: vec![]
394 }],
395 ..Message::default()
396 }
397 .sanitize(
398 true, )
400 .is_ok());
401 }
402
403 #[test]
404 fn test_sanitize_with_table_lookup() {
405 assert!(Message {
406 header: MessageHeader {
407 num_required_signatures: 1,
408 ..MessageHeader::default()
409 },
410 account_keys: vec![Pubkey::new_unique()],
411 address_table_lookups: vec![MessageAddressTableLookup {
412 account_key: Pubkey::new_unique(),
413 writable_indexes: vec![1, 2, 3],
414 readonly_indexes: vec![0],
415 }],
416 ..Message::default()
417 }
418 .sanitize(
419 true, )
421 .is_ok());
422 }
423
424 #[test]
425 fn test_sanitize_with_table_lookup_and_ix_with_dynamic_program_id() {
426 let message = Message {
427 header: MessageHeader {
428 num_required_signatures: 1,
429 ..MessageHeader::default()
430 },
431 account_keys: vec![Pubkey::new_unique()],
432 address_table_lookups: vec![MessageAddressTableLookup {
433 account_key: Pubkey::new_unique(),
434 writable_indexes: vec![1, 2, 3],
435 readonly_indexes: vec![0],
436 }],
437 instructions: vec![CompiledInstruction {
438 program_id_index: 4,
439 accounts: vec![0, 1, 2, 3],
440 data: vec![],
441 }],
442 ..Message::default()
443 };
444
445 assert!(message.sanitize(
446 false, ).is_ok());
448
449 assert!(message.sanitize(
450 true, ).is_err());
452 }
453
454 #[test]
455 fn test_sanitize_with_table_lookup_and_ix_with_static_program_id() {
456 assert!(Message {
457 header: MessageHeader {
458 num_required_signatures: 1,
459 ..MessageHeader::default()
460 },
461 account_keys: vec![Pubkey::new_unique(), Pubkey::new_unique()],
462 address_table_lookups: vec![MessageAddressTableLookup {
463 account_key: Pubkey::new_unique(),
464 writable_indexes: vec![1, 2, 3],
465 readonly_indexes: vec![0],
466 }],
467 instructions: vec![CompiledInstruction {
468 program_id_index: 1,
469 accounts: vec![2, 3, 4, 5],
470 data: vec![]
471 }],
472 ..Message::default()
473 }
474 .sanitize(
475 true, )
477 .is_ok());
478 }
479
480 #[test]
481 fn test_sanitize_without_signer() {
482 assert!(Message {
483 header: MessageHeader::default(),
484 account_keys: vec![Pubkey::new_unique()],
485 ..Message::default()
486 }
487 .sanitize(
488 true, )
490 .is_err());
491 }
492
493 #[test]
494 fn test_sanitize_without_writable_signer() {
495 assert!(Message {
496 header: MessageHeader {
497 num_required_signatures: 1,
498 num_readonly_signed_accounts: 1,
499 ..MessageHeader::default()
500 },
501 account_keys: vec![Pubkey::new_unique()],
502 ..Message::default()
503 }
504 .sanitize(
505 true, )
507 .is_err());
508 }
509
510 #[test]
511 fn test_sanitize_with_empty_table_lookup() {
512 assert!(Message {
513 header: MessageHeader {
514 num_required_signatures: 1,
515 ..MessageHeader::default()
516 },
517 account_keys: vec![Pubkey::new_unique()],
518 address_table_lookups: vec![MessageAddressTableLookup {
519 account_key: Pubkey::new_unique(),
520 writable_indexes: vec![],
521 readonly_indexes: vec![],
522 }],
523 ..Message::default()
524 }
525 .sanitize(
526 true, )
528 .is_err());
529 }
530
531 #[test]
532 fn test_sanitize_with_max_account_keys() {
533 assert!(Message {
534 header: MessageHeader {
535 num_required_signatures: 1,
536 ..MessageHeader::default()
537 },
538 account_keys: (0..=u8::MAX).map(|_| Pubkey::new_unique()).collect(),
539 ..Message::default()
540 }
541 .sanitize(
542 true, )
544 .is_ok());
545 }
546
547 #[test]
548 fn test_sanitize_with_too_many_account_keys() {
549 assert!(Message {
550 header: MessageHeader {
551 num_required_signatures: 1,
552 ..MessageHeader::default()
553 },
554 account_keys: (0..=256).map(|_| Pubkey::new_unique()).collect(),
555 ..Message::default()
556 }
557 .sanitize(
558 true, )
560 .is_err());
561 }
562
563 #[test]
564 fn test_sanitize_with_max_table_loaded_keys() {
565 assert!(Message {
566 header: MessageHeader {
567 num_required_signatures: 1,
568 ..MessageHeader::default()
569 },
570 account_keys: vec![Pubkey::new_unique()],
571 address_table_lookups: vec![MessageAddressTableLookup {
572 account_key: Pubkey::new_unique(),
573 writable_indexes: (0..=254).step_by(2).collect(),
574 readonly_indexes: (1..=254).step_by(2).collect(),
575 }],
576 ..Message::default()
577 }
578 .sanitize(
579 true, )
581 .is_ok());
582 }
583
584 #[test]
585 fn test_sanitize_with_too_many_table_loaded_keys() {
586 assert!(Message {
587 header: MessageHeader {
588 num_required_signatures: 1,
589 ..MessageHeader::default()
590 },
591 account_keys: vec![Pubkey::new_unique()],
592 address_table_lookups: vec![MessageAddressTableLookup {
593 account_key: Pubkey::new_unique(),
594 writable_indexes: (0..=255).step_by(2).collect(),
595 readonly_indexes: (1..=255).step_by(2).collect(),
596 }],
597 ..Message::default()
598 }
599 .sanitize(
600 true, )
602 .is_err());
603 }
604
605 #[test]
606 fn test_sanitize_with_invalid_ix_program_id() {
607 let message = Message {
608 header: MessageHeader {
609 num_required_signatures: 1,
610 ..MessageHeader::default()
611 },
612 account_keys: vec![Pubkey::new_unique()],
613 address_table_lookups: vec![MessageAddressTableLookup {
614 account_key: Pubkey::new_unique(),
615 writable_indexes: vec![0],
616 readonly_indexes: vec![],
617 }],
618 instructions: vec![CompiledInstruction {
619 program_id_index: 2,
620 accounts: vec![],
621 data: vec![],
622 }],
623 ..Message::default()
624 };
625
626 assert!(message
627 .sanitize(true )
628 .is_err());
629 assert!(message
630 .sanitize(false )
631 .is_err());
632 }
633
634 #[test]
635 fn test_sanitize_with_invalid_ix_account() {
636 assert!(Message {
637 header: MessageHeader {
638 num_required_signatures: 1,
639 ..MessageHeader::default()
640 },
641 account_keys: vec![Pubkey::new_unique(), Pubkey::new_unique()],
642 address_table_lookups: vec![MessageAddressTableLookup {
643 account_key: Pubkey::new_unique(),
644 writable_indexes: vec![],
645 readonly_indexes: vec![0],
646 }],
647 instructions: vec![CompiledInstruction {
648 program_id_index: 1,
649 accounts: vec![3],
650 data: vec![]
651 }],
652 ..Message::default()
653 }
654 .sanitize(
655 true, )
657 .is_err());
658 }
659
660 #[test]
661 fn test_serialize() {
662 let message = Message::default();
663 let versioned_msg = VersionedMessage::V0(message.clone());
664 assert_eq!(message.serialize(), versioned_msg.serialize());
665 }
666
667 #[test]
668 fn test_try_compile() {
669 let mut keys = vec![];
670 keys.resize_with(7, Pubkey::new_unique);
671
672 let payer = keys[0];
673 let program_id = keys[6];
674 let instructions = vec![Instruction {
675 program_id,
676 accounts: vec![
677 AccountMeta::new(keys[1], true),
678 AccountMeta::new_readonly(keys[2], true),
679 AccountMeta::new(keys[3], false),
680 AccountMeta::new(keys[4], false), AccountMeta::new_readonly(keys[5], false), ],
683 data: vec![],
684 }];
685 let address_lookup_table_accounts = vec![
686 AddressLookupTableAccount {
687 key: Pubkey::new_unique(),
688 addresses: vec![keys[4], keys[5], keys[6]],
689 },
690 AddressLookupTableAccount {
691 key: Pubkey::new_unique(),
692 addresses: vec![],
693 },
694 ];
695
696 let recent_blockhash = Hash::new_unique();
697 assert_eq!(
698 Message::try_compile(
699 &payer,
700 &instructions,
701 &address_lookup_table_accounts,
702 recent_blockhash
703 ),
704 Ok(Message {
705 header: MessageHeader {
706 num_required_signatures: 3,
707 num_readonly_signed_accounts: 1,
708 num_readonly_unsigned_accounts: 1
709 },
710 recent_blockhash,
711 account_keys: vec![keys[0], keys[1], keys[2], keys[3], program_id],
712 instructions: vec![CompiledInstruction {
713 program_id_index: 4,
714 accounts: vec![1, 2, 3, 5, 6],
715 data: vec![],
716 },],
717 address_table_lookups: vec![MessageAddressTableLookup {
718 account_key: address_lookup_table_accounts[0].key,
719 writable_indexes: vec![0],
720 readonly_indexes: vec![1],
721 }],
722 })
723 );
724 }
725}