1use {
2 crate::{
3 ed25519_program,
4 hash::Hash,
5 instruction::CompiledInstruction,
6 message::{
7 legacy,
8 v0::{self, LoadedAddresses},
9 AccountKeys, AddressLoader, AddressLoaderError, MessageHeader,
10 SanitizedVersionedMessage, VersionedMessage,
11 },
12 nonce::NONCED_TX_MARKER_IX_INDEX,
13 program_utils::limited_deserialize,
14 pubkey::Pubkey,
15 sanitize::{Sanitize, SanitizeError},
16 secp256k1_program,
17 miraland_program::{system_instruction::SystemInstruction, system_program},
18 sysvar::instructions::{BorrowedAccountMeta, BorrowedInstruction},
19 },
20 std::{borrow::Cow, convert::TryFrom},
21 thiserror::Error,
22};
23
24#[derive(Debug, Clone, Eq, PartialEq)]
25pub struct LegacyMessage<'a> {
26 pub message: Cow<'a, legacy::Message>,
28 pub is_writable_account_cache: Vec<bool>,
31}
32
33impl<'a> LegacyMessage<'a> {
34 pub fn new(message: legacy::Message) -> Self {
35 let is_writable_account_cache = message
36 .account_keys
37 .iter()
38 .enumerate()
39 .map(|(i, _key)| message.is_writable(i))
40 .collect::<Vec<_>>();
41 Self {
42 message: Cow::Owned(message),
43 is_writable_account_cache,
44 }
45 }
46
47 pub fn has_duplicates(&self) -> bool {
48 self.message.has_duplicates()
49 }
50
51 pub fn is_key_called_as_program(&self, key_index: usize) -> bool {
52 self.message.is_key_called_as_program(key_index)
53 }
54
55 pub fn is_upgradeable_loader_present(&self) -> bool {
57 self.message.is_upgradeable_loader_present()
58 }
59
60 pub fn account_keys(&self) -> AccountKeys {
62 AccountKeys::new(&self.message.account_keys, None)
63 }
64
65 pub fn is_writable(&self, index: usize) -> bool {
66 *self.is_writable_account_cache.get(index).unwrap_or(&false)
67 }
68}
69
70#[derive(Debug, Clone, Eq, PartialEq)]
72pub enum SanitizedMessage {
73 Legacy(LegacyMessage<'static>),
75 V0(v0::LoadedMessage<'static>),
77}
78
79#[derive(PartialEq, Debug, Error, Eq, Clone)]
80pub enum SanitizeMessageError {
81 #[error("index out of bounds")]
82 IndexOutOfBounds,
83 #[error("value out of bounds")]
84 ValueOutOfBounds,
85 #[error("invalid value")]
86 InvalidValue,
87 #[error("{0}")]
88 AddressLoaderError(#[from] AddressLoaderError),
89}
90
91impl From<SanitizeError> for SanitizeMessageError {
92 fn from(err: SanitizeError) -> Self {
93 match err {
94 SanitizeError::IndexOutOfBounds => Self::IndexOutOfBounds,
95 SanitizeError::ValueOutOfBounds => Self::ValueOutOfBounds,
96 SanitizeError::InvalidValue => Self::InvalidValue,
97 }
98 }
99}
100
101impl TryFrom<legacy::Message> for SanitizedMessage {
102 type Error = SanitizeMessageError;
103 fn try_from(message: legacy::Message) -> Result<Self, Self::Error> {
104 message.sanitize()?;
105 Ok(Self::Legacy(LegacyMessage::new(message)))
106 }
107}
108
109impl SanitizedMessage {
110 pub fn try_new(
114 sanitized_msg: SanitizedVersionedMessage,
115 address_loader: impl AddressLoader,
116 ) -> Result<Self, SanitizeMessageError> {
117 Ok(match sanitized_msg.message {
118 VersionedMessage::Legacy(message) => {
119 SanitizedMessage::Legacy(LegacyMessage::new(message))
120 }
121 VersionedMessage::V0(message) => {
122 let loaded_addresses =
123 address_loader.load_addresses(&message.address_table_lookups)?;
124 SanitizedMessage::V0(v0::LoadedMessage::new(message, loaded_addresses))
125 }
126 })
127 }
128
129 pub fn has_duplicates(&self) -> bool {
131 match self {
132 SanitizedMessage::Legacy(message) => message.has_duplicates(),
133 SanitizedMessage::V0(message) => message.has_duplicates(),
134 }
135 }
136
137 pub fn header(&self) -> &MessageHeader {
140 match self {
141 Self::Legacy(legacy_message) => &legacy_message.message.header,
142 Self::V0(loaded_msg) => &loaded_msg.message.header,
143 }
144 }
145
146 pub fn legacy_message(&self) -> Option<&legacy::Message> {
148 if let Self::Legacy(legacy_message) = &self {
149 Some(&legacy_message.message)
150 } else {
151 None
152 }
153 }
154
155 pub fn fee_payer(&self) -> &Pubkey {
157 self.account_keys()
158 .get(0)
159 .expect("sanitized message always has non-program fee payer at index 0")
160 }
161
162 pub fn recent_blockhash(&self) -> &Hash {
164 match self {
165 Self::Legacy(legacy_message) => &legacy_message.message.recent_blockhash,
166 Self::V0(loaded_msg) => &loaded_msg.message.recent_blockhash,
167 }
168 }
169
170 pub fn instructions(&self) -> &[CompiledInstruction] {
173 match self {
174 Self::Legacy(legacy_message) => &legacy_message.message.instructions,
175 Self::V0(loaded_msg) => &loaded_msg.message.instructions,
176 }
177 }
178
179 pub fn program_instructions_iter(
182 &self,
183 ) -> impl Iterator<Item = (&Pubkey, &CompiledInstruction)> {
184 self.instructions().iter().map(move |ix| {
185 (
186 self.account_keys()
187 .get(usize::from(ix.program_id_index))
188 .expect("program id index is sanitized"),
189 ix,
190 )
191 })
192 }
193
194 pub fn account_keys(&self) -> AccountKeys {
196 match self {
197 Self::Legacy(message) => message.account_keys(),
198 Self::V0(message) => message.account_keys(),
199 }
200 }
201
202 pub fn message_address_table_lookups(&self) -> &[v0::MessageAddressTableLookup] {
204 match self {
205 Self::Legacy(_message) => &[],
206 Self::V0(message) => &message.message.address_table_lookups,
207 }
208 }
209
210 fn is_key_passed_to_program(&self, key_index: usize) -> bool {
213 if let Ok(key_index) = u8::try_from(key_index) {
214 self.instructions()
215 .iter()
216 .any(|ix| ix.accounts.contains(&key_index))
217 } else {
218 false
219 }
220 }
221
222 pub fn is_invoked(&self, key_index: usize) -> bool {
225 match self {
226 Self::Legacy(message) => message.is_key_called_as_program(key_index),
227 Self::V0(message) => message.is_key_called_as_program(key_index),
228 }
229 }
230
231 pub fn is_non_loader_key(&self, key_index: usize) -> bool {
234 !self.is_invoked(key_index) || self.is_key_passed_to_program(key_index)
235 }
236
237 pub fn is_writable(&self, index: usize) -> bool {
240 match self {
241 Self::Legacy(message) => message.is_writable(index),
242 Self::V0(message) => message.is_writable(index),
243 }
244 }
245
246 pub fn is_signer(&self, index: usize) -> bool {
249 index < usize::from(self.header().num_required_signatures)
250 }
251
252 fn loaded_lookup_table_addresses(&self) -> Option<&LoadedAddresses> {
254 match &self {
255 SanitizedMessage::V0(message) => Some(&message.loaded_addresses),
256 _ => None,
257 }
258 }
259
260 pub fn num_readonly_accounts(&self) -> usize {
262 let loaded_readonly_addresses = self
263 .loaded_lookup_table_addresses()
264 .map(|keys| keys.readonly.len())
265 .unwrap_or_default();
266 loaded_readonly_addresses
267 .saturating_add(usize::from(self.header().num_readonly_signed_accounts))
268 .saturating_add(usize::from(self.header().num_readonly_unsigned_accounts))
269 }
270
271 pub fn decompile_instructions(&self) -> Vec<BorrowedInstruction> {
273 let account_keys = self.account_keys();
274 self.program_instructions_iter()
275 .map(|(program_id, instruction)| {
276 let accounts = instruction
277 .accounts
278 .iter()
279 .map(|account_index| {
280 let account_index = *account_index as usize;
281 BorrowedAccountMeta {
282 is_signer: self.is_signer(account_index),
283 is_writable: self.is_writable(account_index),
284 pubkey: account_keys.get(account_index).unwrap(),
285 }
286 })
287 .collect();
288
289 BorrowedInstruction {
290 accounts,
291 data: &instruction.data,
292 program_id,
293 }
294 })
295 .collect()
296 }
297
298 pub fn is_upgradeable_loader_present(&self) -> bool {
300 match self {
301 Self::Legacy(message) => message.is_upgradeable_loader_present(),
302 Self::V0(message) => message.is_upgradeable_loader_present(),
303 }
304 }
305
306 pub fn get_ix_signers(&self, ix_index: usize) -> impl Iterator<Item = &Pubkey> {
308 self.instructions()
309 .get(ix_index)
310 .into_iter()
311 .flat_map(|ix| {
312 ix.accounts
313 .iter()
314 .copied()
315 .map(usize::from)
316 .filter(|index| self.is_signer(*index))
317 .filter_map(|signer_index| self.account_keys().get(signer_index))
318 })
319 }
320
321 pub fn get_durable_nonce(&self) -> Option<&Pubkey> {
323 self.instructions()
324 .get(NONCED_TX_MARKER_IX_INDEX as usize)
325 .filter(
326 |ix| match self.account_keys().get(ix.program_id_index as usize) {
327 Some(program_id) => system_program::check_id(program_id),
328 _ => false,
329 },
330 )
331 .filter(|ix| {
332 matches!(
333 limited_deserialize(&ix.data, 4 ),
334 Ok(SystemInstruction::AdvanceNonceAccount)
335 )
336 })
337 .and_then(|ix| {
338 ix.accounts.first().and_then(|idx| {
339 let idx = *idx as usize;
340 if !self.is_writable(idx) {
341 None
342 } else {
343 self.account_keys().get(idx)
344 }
345 })
346 })
347 }
348
349 pub fn num_signatures(&self) -> u64 {
350 let mut num_signatures = u64::from(self.header().num_required_signatures);
351 for (program_id, instruction) in self.program_instructions_iter() {
354 if secp256k1_program::check_id(program_id) || ed25519_program::check_id(program_id) {
355 if let Some(num_verifies) = instruction.data.first() {
356 num_signatures = num_signatures.saturating_add(u64::from(*num_verifies));
357 }
358 }
359 }
360 num_signatures
361 }
362
363 pub fn num_write_locks(&self) -> u64 {
366 self.account_keys()
367 .len()
368 .saturating_sub(self.num_readonly_accounts()) as u64
369 }
370}
371
372#[cfg(test)]
373mod tests {
374 use {super::*, crate::message::v0, std::collections::HashSet};
375
376 #[test]
377 fn test_try_from_message() {
378 let legacy_message_with_no_signers = legacy::Message {
379 account_keys: vec![Pubkey::new_unique()],
380 ..legacy::Message::default()
381 };
382
383 assert_eq!(
384 SanitizedMessage::try_from(legacy_message_with_no_signers).err(),
385 Some(SanitizeMessageError::IndexOutOfBounds),
386 );
387 }
388
389 #[test]
390 fn test_is_non_loader_key() {
391 let key0 = Pubkey::new_unique();
392 let key1 = Pubkey::new_unique();
393 let loader_key = Pubkey::new_unique();
394 let instructions = vec![
395 CompiledInstruction::new(1, &(), vec![0]),
396 CompiledInstruction::new(2, &(), vec![0, 1]),
397 ];
398
399 let message = SanitizedMessage::try_from(legacy::Message::new_with_compiled_instructions(
400 1,
401 0,
402 2,
403 vec![key0, key1, loader_key],
404 Hash::default(),
405 instructions,
406 ))
407 .unwrap();
408
409 assert!(message.is_non_loader_key(0));
410 assert!(message.is_non_loader_key(1));
411 assert!(!message.is_non_loader_key(2));
412 }
413
414 #[test]
415 fn test_num_readonly_accounts() {
416 let key0 = Pubkey::new_unique();
417 let key1 = Pubkey::new_unique();
418 let key2 = Pubkey::new_unique();
419 let key3 = Pubkey::new_unique();
420 let key4 = Pubkey::new_unique();
421 let key5 = Pubkey::new_unique();
422
423 let legacy_message = SanitizedMessage::try_from(legacy::Message {
424 header: MessageHeader {
425 num_required_signatures: 2,
426 num_readonly_signed_accounts: 1,
427 num_readonly_unsigned_accounts: 1,
428 },
429 account_keys: vec![key0, key1, key2, key3],
430 ..legacy::Message::default()
431 })
432 .unwrap();
433
434 assert_eq!(legacy_message.num_readonly_accounts(), 2);
435
436 let v0_message = SanitizedMessage::V0(v0::LoadedMessage::new(
437 v0::Message {
438 header: MessageHeader {
439 num_required_signatures: 2,
440 num_readonly_signed_accounts: 1,
441 num_readonly_unsigned_accounts: 1,
442 },
443 account_keys: vec![key0, key1, key2, key3],
444 ..v0::Message::default()
445 },
446 LoadedAddresses {
447 writable: vec![key4],
448 readonly: vec![key5],
449 },
450 ));
451
452 assert_eq!(v0_message.num_readonly_accounts(), 3);
453 }
454
455 #[test]
456 fn test_get_ix_signers() {
457 let signer0 = Pubkey::new_unique();
458 let signer1 = Pubkey::new_unique();
459 let non_signer = Pubkey::new_unique();
460 let loader_key = Pubkey::new_unique();
461 let instructions = vec![
462 CompiledInstruction::new(3, &(), vec![2, 0]),
463 CompiledInstruction::new(3, &(), vec![0, 1]),
464 CompiledInstruction::new(3, &(), vec![0, 0]),
465 ];
466
467 let message = SanitizedMessage::try_from(legacy::Message::new_with_compiled_instructions(
468 2,
469 1,
470 2,
471 vec![signer0, signer1, non_signer, loader_key],
472 Hash::default(),
473 instructions,
474 ))
475 .unwrap();
476
477 assert_eq!(
478 message.get_ix_signers(0).collect::<HashSet<_>>(),
479 HashSet::from_iter([&signer0])
480 );
481 assert_eq!(
482 message.get_ix_signers(1).collect::<HashSet<_>>(),
483 HashSet::from_iter([&signer0, &signer1])
484 );
485 assert_eq!(
486 message.get_ix_signers(2).collect::<HashSet<_>>(),
487 HashSet::from_iter([&signer0])
488 );
489 assert_eq!(
490 message.get_ix_signers(3).collect::<HashSet<_>>(),
491 HashSet::default()
492 );
493 }
494
495 #[test]
496 #[allow(clippy::get_first)]
497 fn test_is_writable_account_cache() {
498 let key0 = Pubkey::new_unique();
499 let key1 = Pubkey::new_unique();
500 let key2 = Pubkey::new_unique();
501 let key3 = Pubkey::new_unique();
502 let key4 = Pubkey::new_unique();
503 let key5 = Pubkey::new_unique();
504
505 let legacy_message = SanitizedMessage::try_from(legacy::Message {
506 header: MessageHeader {
507 num_required_signatures: 2,
508 num_readonly_signed_accounts: 1,
509 num_readonly_unsigned_accounts: 1,
510 },
511 account_keys: vec![key0, key1, key2, key3],
512 ..legacy::Message::default()
513 })
514 .unwrap();
515 match legacy_message {
516 SanitizedMessage::Legacy(message) => {
517 assert_eq!(
518 message.is_writable_account_cache.len(),
519 message.account_keys().len()
520 );
521 assert!(message.is_writable_account_cache.get(0).unwrap());
522 assert!(!message.is_writable_account_cache.get(1).unwrap());
523 assert!(message.is_writable_account_cache.get(2).unwrap());
524 assert!(!message.is_writable_account_cache.get(3).unwrap());
525 }
526 _ => {
527 panic!("Expect to be SanitizedMessage::LegacyMessage")
528 }
529 }
530
531 let v0_message = SanitizedMessage::V0(v0::LoadedMessage::new(
532 v0::Message {
533 header: MessageHeader {
534 num_required_signatures: 2,
535 num_readonly_signed_accounts: 1,
536 num_readonly_unsigned_accounts: 1,
537 },
538 account_keys: vec![key0, key1, key2, key3],
539 ..v0::Message::default()
540 },
541 LoadedAddresses {
542 writable: vec![key4],
543 readonly: vec![key5],
544 },
545 ));
546 match v0_message {
547 SanitizedMessage::V0(message) => {
548 assert_eq!(
549 message.is_writable_account_cache.len(),
550 message.account_keys().len()
551 );
552 assert!(message.is_writable_account_cache.get(0).unwrap());
553 assert!(!message.is_writable_account_cache.get(1).unwrap());
554 assert!(message.is_writable_account_cache.get(2).unwrap());
555 assert!(!message.is_writable_account_cache.get(3).unwrap());
556 assert!(message.is_writable_account_cache.get(4).unwrap());
557 assert!(!message.is_writable_account_cache.get(5).unwrap());
558 }
559 _ => {
560 panic!("Expect to be SanitizedMessage::V0")
561 }
562 }
563 }
564}