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