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) -> 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);
136 if total_account_keys > 256 {
137 return Err(SanitizeError::IndexOutOfBounds);
138 }
139
140 let max_account_ix = total_account_keys
143 .checked_sub(1)
144 .expect("message doesn't contain any account keys");
145
146 let max_program_id_ix =
150 num_static_account_keys
153 .checked_sub(1)
154 .expect("message doesn't contain any static account keys");
155
156 for ci in &self.instructions {
157 if usize::from(ci.program_id_index) > max_program_id_ix {
158 return Err(SanitizeError::IndexOutOfBounds);
159 }
160 if ci.program_id_index == 0 {
162 return Err(SanitizeError::IndexOutOfBounds);
163 }
164 for ai in &ci.accounts {
165 if usize::from(*ai) > max_account_ix {
166 return Err(SanitizeError::IndexOutOfBounds);
167 }
168 }
169 }
170
171 Ok(())
172 }
173}
174
175impl Message {
176 pub fn try_compile(
255 payer: &Pubkey,
256 instructions: &[Instruction],
257 address_lookup_table_accounts: &[AddressLookupTableAccount],
258 recent_blockhash: Hash,
259 ) -> Result<Self, CompileError> {
260 let mut compiled_keys = CompiledKeys::compile(instructions, Some(*payer));
261
262 let mut address_table_lookups = Vec::with_capacity(address_lookup_table_accounts.len());
263 let mut loaded_addresses_list = Vec::with_capacity(address_lookup_table_accounts.len());
264 for lookup_table_account in address_lookup_table_accounts {
265 if let Some((lookup, loaded_addresses)) =
266 compiled_keys.try_extract_table_lookup(lookup_table_account)?
267 {
268 address_table_lookups.push(lookup);
269 loaded_addresses_list.push(loaded_addresses);
270 }
271 }
272
273 let (header, static_keys) = compiled_keys.try_into_message_components()?;
274 let dynamic_keys = loaded_addresses_list.into_iter().collect();
275 let account_keys = AccountKeys::new(&static_keys, Some(&dynamic_keys));
276 let instructions = account_keys.try_compile_instructions(instructions)?;
277
278 Ok(Self {
279 header,
280 account_keys: static_keys,
281 recent_blockhash,
282 instructions,
283 address_table_lookups,
284 })
285 }
286
287 pub fn serialize(&self) -> Vec<u8> {
289 bincode::serialize(&(MESSAGE_VERSION_PREFIX, self)).unwrap()
290 }
291
292 pub fn is_key_called_as_program(&self, key_index: usize) -> bool {
294 if let Ok(key_index) = u8::try_from(key_index) {
295 self.instructions
296 .iter()
297 .any(|ix| ix.program_id_index == key_index)
298 } else {
299 false
300 }
301 }
302
303 fn is_writable_index(&self, key_index: usize) -> bool {
306 let header = &self.header;
307 let num_account_keys = self.account_keys.len();
308 let num_signed_accounts = usize::from(header.num_required_signatures);
309 if key_index >= num_account_keys {
310 let loaded_addresses_index = key_index.saturating_sub(num_account_keys);
311 let num_writable_dynamic_addresses = self
312 .address_table_lookups
313 .iter()
314 .map(|lookup| lookup.writable_indexes.len())
315 .sum();
316 loaded_addresses_index < num_writable_dynamic_addresses
317 } else if key_index >= num_signed_accounts {
318 let num_unsigned_accounts = num_account_keys.saturating_sub(num_signed_accounts);
319 let num_writable_unsigned_accounts = num_unsigned_accounts
320 .saturating_sub(usize::from(header.num_readonly_unsigned_accounts));
321 let unsigned_account_index = key_index.saturating_sub(num_signed_accounts);
322 unsigned_account_index < num_writable_unsigned_accounts
323 } else {
324 let num_writable_signed_accounts = num_signed_accounts
325 .saturating_sub(usize::from(header.num_readonly_signed_accounts));
326 key_index < num_writable_signed_accounts
327 }
328 }
329
330 fn is_upgradeable_loader_in_static_keys(&self) -> bool {
332 self.account_keys
333 .iter()
334 .any(|&key| key == bpf_loader_upgradeable::id())
335 }
336
337 pub fn is_maybe_writable(&self, key_index: usize) -> bool {
341 self.is_writable_index(key_index)
342 && !{
343 self.account_keys
345 .get(key_index)
346 .map(is_builtin_key_or_sysvar)
347 .unwrap_or_default()
348 }
349 && !{
350 self.is_key_called_as_program(key_index)
352 && !self.is_upgradeable_loader_in_static_keys()
353 }
354 }
355}
356
357#[cfg(test)]
358mod tests {
359 use {
360 super::*,
361 crate::{instruction::AccountMeta, message::VersionedMessage},
362 };
363
364 #[test]
365 fn test_sanitize() {
366 assert!(Message {
367 header: MessageHeader {
368 num_required_signatures: 1,
369 ..MessageHeader::default()
370 },
371 account_keys: vec![Pubkey::new_unique()],
372 ..Message::default()
373 }
374 .sanitize()
375 .is_ok());
376 }
377
378 #[test]
379 fn test_sanitize_with_instruction() {
380 assert!(Message {
381 header: MessageHeader {
382 num_required_signatures: 1,
383 ..MessageHeader::default()
384 },
385 account_keys: vec![Pubkey::new_unique(), Pubkey::new_unique()],
386 instructions: vec![CompiledInstruction {
387 program_id_index: 1,
388 accounts: vec![0],
389 data: vec![]
390 }],
391 ..Message::default()
392 }
393 .sanitize()
394 .is_ok());
395 }
396
397 #[test]
398 fn test_sanitize_with_table_lookup() {
399 assert!(Message {
400 header: MessageHeader {
401 num_required_signatures: 1,
402 ..MessageHeader::default()
403 },
404 account_keys: vec![Pubkey::new_unique()],
405 address_table_lookups: vec![MessageAddressTableLookup {
406 account_key: Pubkey::new_unique(),
407 writable_indexes: vec![1, 2, 3],
408 readonly_indexes: vec![0],
409 }],
410 ..Message::default()
411 }
412 .sanitize()
413 .is_ok());
414 }
415
416 #[test]
417 fn test_sanitize_with_table_lookup_and_ix_with_dynamic_program_id() {
418 let message = Message {
419 header: MessageHeader {
420 num_required_signatures: 1,
421 ..MessageHeader::default()
422 },
423 account_keys: vec![Pubkey::new_unique()],
424 address_table_lookups: vec![MessageAddressTableLookup {
425 account_key: Pubkey::new_unique(),
426 writable_indexes: vec![1, 2, 3],
427 readonly_indexes: vec![0],
428 }],
429 instructions: vec![CompiledInstruction {
430 program_id_index: 4,
431 accounts: vec![0, 1, 2, 3],
432 data: vec![],
433 }],
434 ..Message::default()
435 };
436
437 assert!(message.sanitize().is_err());
438 }
439
440 #[test]
441 fn test_sanitize_with_table_lookup_and_ix_with_static_program_id() {
442 assert!(Message {
443 header: MessageHeader {
444 num_required_signatures: 1,
445 ..MessageHeader::default()
446 },
447 account_keys: vec![Pubkey::new_unique(), Pubkey::new_unique()],
448 address_table_lookups: vec![MessageAddressTableLookup {
449 account_key: Pubkey::new_unique(),
450 writable_indexes: vec![1, 2, 3],
451 readonly_indexes: vec![0],
452 }],
453 instructions: vec![CompiledInstruction {
454 program_id_index: 1,
455 accounts: vec![2, 3, 4, 5],
456 data: vec![]
457 }],
458 ..Message::default()
459 }
460 .sanitize()
461 .is_ok());
462 }
463
464 #[test]
465 fn test_sanitize_without_signer() {
466 assert!(Message {
467 header: MessageHeader::default(),
468 account_keys: vec![Pubkey::new_unique()],
469 ..Message::default()
470 }
471 .sanitize()
472 .is_err());
473 }
474
475 #[test]
476 fn test_sanitize_without_writable_signer() {
477 assert!(Message {
478 header: MessageHeader {
479 num_required_signatures: 1,
480 num_readonly_signed_accounts: 1,
481 ..MessageHeader::default()
482 },
483 account_keys: vec![Pubkey::new_unique()],
484 ..Message::default()
485 }
486 .sanitize()
487 .is_err());
488 }
489
490 #[test]
491 fn test_sanitize_with_empty_table_lookup() {
492 assert!(Message {
493 header: MessageHeader {
494 num_required_signatures: 1,
495 ..MessageHeader::default()
496 },
497 account_keys: vec![Pubkey::new_unique()],
498 address_table_lookups: vec![MessageAddressTableLookup {
499 account_key: Pubkey::new_unique(),
500 writable_indexes: vec![],
501 readonly_indexes: vec![],
502 }],
503 ..Message::default()
504 }
505 .sanitize()
506 .is_err());
507 }
508
509 #[test]
510 fn test_sanitize_with_max_account_keys() {
511 assert!(Message {
512 header: MessageHeader {
513 num_required_signatures: 1,
514 ..MessageHeader::default()
515 },
516 account_keys: (0..=u8::MAX).map(|_| Pubkey::new_unique()).collect(),
517 ..Message::default()
518 }
519 .sanitize()
520 .is_ok());
521 }
522
523 #[test]
524 fn test_sanitize_with_too_many_account_keys() {
525 assert!(Message {
526 header: MessageHeader {
527 num_required_signatures: 1,
528 ..MessageHeader::default()
529 },
530 account_keys: (0..=256).map(|_| Pubkey::new_unique()).collect(),
531 ..Message::default()
532 }
533 .sanitize()
534 .is_err());
535 }
536
537 #[test]
538 fn test_sanitize_with_max_table_loaded_keys() {
539 assert!(Message {
540 header: MessageHeader {
541 num_required_signatures: 1,
542 ..MessageHeader::default()
543 },
544 account_keys: vec![Pubkey::new_unique()],
545 address_table_lookups: vec![MessageAddressTableLookup {
546 account_key: Pubkey::new_unique(),
547 writable_indexes: (0..=254).step_by(2).collect(),
548 readonly_indexes: (1..=254).step_by(2).collect(),
549 }],
550 ..Message::default()
551 }
552 .sanitize()
553 .is_ok());
554 }
555
556 #[test]
557 fn test_sanitize_with_too_many_table_loaded_keys() {
558 assert!(Message {
559 header: MessageHeader {
560 num_required_signatures: 1,
561 ..MessageHeader::default()
562 },
563 account_keys: vec![Pubkey::new_unique()],
564 address_table_lookups: vec![MessageAddressTableLookup {
565 account_key: Pubkey::new_unique(),
566 writable_indexes: (0..=255).step_by(2).collect(),
567 readonly_indexes: (1..=255).step_by(2).collect(),
568 }],
569 ..Message::default()
570 }
571 .sanitize()
572 .is_err());
573 }
574
575 #[test]
576 fn test_sanitize_with_invalid_ix_program_id() {
577 let message = Message {
578 header: MessageHeader {
579 num_required_signatures: 1,
580 ..MessageHeader::default()
581 },
582 account_keys: vec![Pubkey::new_unique()],
583 address_table_lookups: vec![MessageAddressTableLookup {
584 account_key: Pubkey::new_unique(),
585 writable_indexes: vec![0],
586 readonly_indexes: vec![],
587 }],
588 instructions: vec![CompiledInstruction {
589 program_id_index: 2,
590 accounts: vec![],
591 data: vec![],
592 }],
593 ..Message::default()
594 };
595
596 assert!(message.sanitize().is_err());
597 }
598
599 #[test]
600 fn test_sanitize_with_invalid_ix_account() {
601 assert!(Message {
602 header: MessageHeader {
603 num_required_signatures: 1,
604 ..MessageHeader::default()
605 },
606 account_keys: vec![Pubkey::new_unique(), Pubkey::new_unique()],
607 address_table_lookups: vec![MessageAddressTableLookup {
608 account_key: Pubkey::new_unique(),
609 writable_indexes: vec![],
610 readonly_indexes: vec![0],
611 }],
612 instructions: vec![CompiledInstruction {
613 program_id_index: 1,
614 accounts: vec![3],
615 data: vec![]
616 }],
617 ..Message::default()
618 }
619 .sanitize()
620 .is_err());
621 }
622
623 #[test]
624 fn test_serialize() {
625 let message = Message::default();
626 let versioned_msg = VersionedMessage::V0(message.clone());
627 assert_eq!(message.serialize(), versioned_msg.serialize());
628 }
629
630 #[test]
631 fn test_try_compile() {
632 let mut keys = vec![];
633 keys.resize_with(7, Pubkey::new_unique);
634
635 let payer = keys[0];
636 let program_id = keys[6];
637 let instructions = vec![Instruction {
638 program_id,
639 accounts: vec![
640 AccountMeta::new(keys[1], true),
641 AccountMeta::new_readonly(keys[2], true),
642 AccountMeta::new(keys[3], false),
643 AccountMeta::new(keys[4], false), AccountMeta::new_readonly(keys[5], false), ],
646 data: vec![],
647 }];
648 let address_lookup_table_accounts = vec![
649 AddressLookupTableAccount {
650 key: Pubkey::new_unique(),
651 addresses: vec![keys[4], keys[5], keys[6]],
652 },
653 AddressLookupTableAccount {
654 key: Pubkey::new_unique(),
655 addresses: vec![],
656 },
657 ];
658
659 let recent_blockhash = Hash::new_unique();
660 assert_eq!(
661 Message::try_compile(
662 &payer,
663 &instructions,
664 &address_lookup_table_accounts,
665 recent_blockhash
666 ),
667 Ok(Message {
668 header: MessageHeader {
669 num_required_signatures: 3,
670 num_readonly_signed_accounts: 1,
671 num_readonly_unsigned_accounts: 1
672 },
673 recent_blockhash,
674 account_keys: vec![keys[0], keys[1], keys[2], keys[3], program_id],
675 instructions: vec![CompiledInstruction {
676 program_id_index: 4,
677 accounts: vec![1, 2, 3, 5, 6],
678 data: vec![],
679 },],
680 address_table_lookups: vec![MessageAddressTableLookup {
681 account_key: address_lookup_table_accounts[0].key,
682 writable_indexes: vec![0],
683 readonly_indexes: vec![1],
684 }],
685 })
686 );
687 }
688}