solana_program/message/versions/v0/
mod.rs

1//! A future Safecoin message format.
2//!
3//! This crate defines two versions of `Message` in their own modules:
4//! [`legacy`] and [`v0`]. `legacy` is the current version as of Safecoin 1.10.0.
5//! `v0` is a [future message format] that encodes more account keys into a
6//! transaction than the legacy format.
7//!
8//! [`legacy`]: crate::message::legacy
9//! [`v0`]: crate::message::v0
10//! [future message format]: https://docs.solana.com/proposals/transactions-v2
11
12use 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/// Address table lookups describe an on-chain address lookup table to use
31/// for loading more readonly and writable accounts in a single tx.
32#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, AbiExample)]
33#[serde(rename_all = "camelCase")]
34pub struct MessageAddressTableLookup {
35    /// Address lookup table account key
36    pub account_key: Pubkey,
37    /// List of indexes used to load writable account addresses
38    #[serde(with = "short_vec")]
39    pub writable_indexes: Vec<u8>,
40    /// List of indexes used to load readonly account addresses
41    #[serde(with = "short_vec")]
42    pub readonly_indexes: Vec<u8>,
43}
44
45/// A Safecoin transaction message (v0).
46///
47/// This message format supports succinct account loading with
48/// on-chain address lookup tables.
49///
50/// See the [`message`] module documentation for further description.
51///
52/// [`message`]: crate::message
53#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, AbiExample)]
54#[serde(rename_all = "camelCase")]
55pub struct Message {
56    /// The message header, identifying signed and read-only `account_keys`.
57    /// Header values only describe static `account_keys`, they do not describe
58    /// any additional account keys loaded via address table lookups.
59    pub header: MessageHeader,
60
61    /// List of accounts loaded by this transaction.
62    #[serde(with = "short_vec")]
63    pub account_keys: Vec<Pubkey>,
64
65    /// The blockhash of a recent block.
66    pub recent_blockhash: Hash,
67
68    /// Instructions that invoke a designated program, are executed in sequence,
69    /// and committed in one atomic transaction if all succeed.
70    ///
71    /// # Notes
72    ///
73    /// Program indexes must index into the list of message `account_keys` because
74    /// program id's cannot be dynamically loaded from a lookup table.
75    ///
76    /// Account indexes must index into the list of addresses
77    /// constructed from the concatenation of three key lists:
78    ///   1) message `account_keys`
79    ///   2) ordered list of keys loaded from `writable` lookup table indexes
80    ///   3) ordered list of keys loaded from `readable` lookup table indexes
81    #[serde(with = "short_vec")]
82    pub instructions: Vec<CompiledInstruction>,
83
84    /// List of address table lookups used to load additional accounts
85    /// for this transaction.
86    #[serde(with = "short_vec")]
87    pub address_table_lookups: Vec<MessageAddressTableLookup>,
88}
89
90impl Message {
91    /// Sanitize message fields and compiled instruction indexes
92    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        // there should be at least 1 RW fee-payer account.
102        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                // each lookup table must be used to load at least one account
115                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        // this is redundant with the above sanitization checks which require that:
125        // 1) the header describes at least 1 RW account
126        // 2) the header doesn't describe more account keys than the number of account keys
127        if num_static_account_keys == 0 {
128            return Err(SanitizeError::InvalidValue);
129        }
130
131        // the combined number of static and dynamic account keys must be <= 256
132        // since account indices are encoded as `u8`
133        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        // `expect` is safe because of earlier check that
139        // `num_static_account_keys` is non-zero
140        let max_account_ix = total_account_keys
141            .checked_sub(1)
142            .expect("message doesn't contain any account keys");
143
144        // switch to rejecting program ids loaded from lookup tables so that
145        // static analysis on program instructions can be performed without
146        // loading on-chain data from a bank
147        let max_program_id_ix = if reject_dynamic_program_ids {
148            // `expect` is safe because of earlier check that
149            // `num_static_account_keys` is non-zero
150            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            // A program cannot be a payer.
162            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    /// Create a signable transaction message from a `payer` public key,
178    /// `recent_blockhash`, list of `instructions`, and a list of
179    /// `address_lookup_table_accounts`.
180    ///
181    /// # Examples
182    ///
183    /// This example uses the [`solana_address_lookup_table_program`], [`safecoin_client`], [`solana_sdk`], and [`anyhow`] crates.
184    ///
185    /// [`solana_address_lookup_table_program`]: https://docs.rs/safecoin-address-lookup-table-program
186    /// [`safecoin_client`]: https://docs.rs/safecoin-client
187    /// [`solana_sdk`]: https://docs.rs/safecoin-sdk
188    /// [`anyhow`]: https://docs.rs/anyhow
189    ///
190    /// ```
191    /// # use solana_program::example_mocks::{
192    /// #     solana_address_lookup_table_program,
193    /// #     safecoin_client,
194    /// #     solana_sdk,
195    /// # };
196    /// # use std::borrow::Cow;
197    /// # use solana_sdk::account::Account;
198    /// use anyhow::Result;
199    /// use solana_address_lookup_table_program::state::AddressLookupTable;
200    /// use safecoin_client::rpc_client::RpcClient;
201    /// use solana_sdk::{
202    ///      address_lookup_table_account::AddressLookupTableAccount,
203    ///      instruction::{AccountMeta, Instruction},
204    ///      message::{VersionedMessage, v0},
205    ///      pubkey::Pubkey,
206    ///      signature::{Keypair, Signer},
207    ///      transaction::VersionedTransaction,
208    /// };
209    ///
210    /// fn create_tx_with_address_table_lookup(
211    ///     client: &RpcClient,
212    ///     instruction: Instruction,
213    ///     address_lookup_table_key: Pubkey,
214    ///     payer: &Keypair,
215    /// ) -> Result<VersionedTransaction> {
216    ///     # client.set_get_account_response(address_lookup_table_key, Account {
217    ///     #   lamports: 1,
218    ///     #   data: AddressLookupTable {
219    ///     #     addresses: Cow::Owned(instruction.accounts.iter().map(|meta| meta.pubkey).collect()),
220    ///     #   }.serialize_for_tests().unwrap(),
221    ///     #   owner: solana_address_lookup_table_program::ID,
222    ///     #   executable: false,
223    ///     #   rent_epoch: 1,
224    ///     # });
225    ///     let raw_account = client.get_account(&address_lookup_table_key)?;
226    ///     let address_lookup_table = AddressLookupTable::deserialize(&raw_account.data)?;
227    ///     let address_lookup_table_account = AddressLookupTableAccount {
228    ///         key: address_lookup_table_key,
229    ///         addresses: address_lookup_table.addresses.to_vec(),
230    ///     };
231    ///
232    ///     let blockhash = client.get_latest_blockhash()?;
233    ///     let tx = VersionedTransaction::try_new(
234    ///         VersionedMessage::V0(v0::Message::try_compile(
235    ///             &payer.pubkey(),
236    ///             &[instruction],
237    ///             &[address_lookup_table_account],
238    ///             blockhash,
239    ///         )?),
240    ///         &[payer],
241    ///     )?;
242    ///
243    ///     # assert!(tx.message.address_table_lookups().unwrap().len() > 0);
244    ///     Ok(tx)
245    /// }
246    /// #
247    /// # let client = RpcClient::new(String::new());
248    /// # let payer = Keypair::new();
249    /// # let address_lookup_table_key = Pubkey::new_unique();
250    /// # let instruction = Instruction::new_with_bincode(Pubkey::new_unique(), &(), vec![
251    /// #   AccountMeta::new(Pubkey::new_unique(), false),
252    /// # ]);
253    /// # create_tx_with_address_table_lookup(&client, instruction, address_lookup_table_key, &payer)?;
254    /// # Ok::<(), anyhow::Error>(())
255    /// ```
256    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    /// Serialize this message with a version #0 prefix using bincode encoding.
290    pub fn serialize(&self) -> Vec<u8> {
291        bincode::serialize(&(MESSAGE_VERSION_PREFIX, self)).unwrap()
292    }
293
294    /// Returns true if the account at the specified index is called as a program by an instruction
295    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    /// Returns true if the account at the specified index was requested to be
306    /// writable.  This method should not be used directly.
307    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    /// Returns true if any static account key is the bpf upgradeable loader
333    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    /// Returns true if the account at the specified index was requested as writable.
340    /// Before loading addresses, we can't demote write locks for dynamically loaded
341    /// addresses so this should not be used by the runtime.
342    pub fn is_maybe_writable(&self, key_index: usize) -> bool {
343        self.is_writable_index(key_index)
344            && !{
345                // demote reserved ids
346                self.account_keys
347                    .get(key_index)
348                    .map(is_builtin_key_or_sysvar)
349                    .unwrap_or_default()
350            }
351            && !{
352                // demote program ids
353                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, // require_static_program_ids
378        )
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, // require_static_program_ids
399        )
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, // require_static_program_ids
420        )
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, // require_static_program_ids
447        ).is_ok());
448
449        assert!(message.sanitize(
450            true, // require_static_program_ids
451        ).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, // require_static_program_ids
476        )
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, // require_static_program_ids
489        )
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, // require_static_program_ids
506        )
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, // require_static_program_ids
527        )
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, // require_static_program_ids
543        )
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, // require_static_program_ids
559        )
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, // require_static_program_ids
580        )
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, // require_static_program_ids
601        )
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 /* require_static_program_ids */)
628            .is_err());
629        assert!(message
630            .sanitize(false /* require_static_program_ids */)
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, // require_static_program_ids
656        )
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), // loaded from lut
681                AccountMeta::new_readonly(keys[5], false), // loaded from lut
682            ],
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}