Skip to main content

solana_transaction_context/
transaction_accounts.rs

1#[cfg(feature = "dev-context-only-utils")]
2use qualifier_attr::qualifiers;
3use {
4    crate::{
5        IndexOfAccount, MAX_ACCOUNT_DATA_GROWTH_PER_TRANSACTION, MAX_ACCOUNT_DATA_LEN,
6        vm_addresses::{GUEST_ACCOUNT_PAYLOAD_BASE_ADDRESS, GUEST_REGION_SIZE},
7        vm_slice::VmSlice,
8    },
9    solana_account::{AccountSharedData, ReadableAccount, WritableAccount},
10    solana_instruction::error::InstructionError,
11    solana_pubkey::Pubkey,
12    std::{
13        cell::{Cell, UnsafeCell},
14        ops::{Deref, DerefMut},
15        ptr,
16        sync::Arc,
17    },
18};
19
20/// This struct is shared with programs. Do not alter its fields.
21#[repr(C)]
22#[derive(Debug, PartialEq)]
23struct AccountSharedFields {
24    key: Pubkey,
25    owner: Pubkey,
26    lamports: u64,
27    // The payload is going to be filled with the guest virtual address of the account payload
28    // vector.
29    payload: VmSlice<u8>,
30}
31
32#[derive(Debug, PartialEq)]
33#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
34struct AccountPrivateFields {
35    rent_epoch: u64,
36    executable: bool,
37    payload: Arc<Vec<u8>>,
38}
39
40#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
41impl AccountPrivateFields {
42    fn payload_len(&self) -> usize {
43        self.payload.len()
44    }
45}
46
47#[derive(Debug, PartialEq)]
48#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
49pub struct TransactionAccountView<'a> {
50    abi_account: &'a AccountSharedFields,
51    private_fields: &'a AccountPrivateFields,
52}
53
54#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
55impl ReadableAccount for TransactionAccountView<'_> {
56    fn lamports(&self) -> u64 {
57        self.abi_account.lamports
58    }
59
60    fn data(&self) -> &[u8] {
61        self.private_fields.payload.as_slice()
62    }
63
64    fn owner(&self) -> &Pubkey {
65        &self.abi_account.owner
66    }
67
68    fn executable(&self) -> bool {
69        self.private_fields.executable
70    }
71
72    fn rent_epoch(&self) -> u64 {
73        self.private_fields.rent_epoch
74    }
75}
76
77#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
78impl PartialEq<AccountSharedData> for TransactionAccountView<'_> {
79    fn eq(&self, other: &AccountSharedData) -> bool {
80        other.lamports() == self.lamports()
81            && other.data() == self.data()
82            && other.owner() == self.owner()
83            && other.executable() == self.executable()
84            && other.rent_epoch() == self.rent_epoch()
85    }
86}
87
88#[derive(Debug)]
89#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
90pub struct TransactionAccountViewMut<'a> {
91    abi_account: &'a mut AccountSharedFields,
92    private_fields: &'a mut AccountPrivateFields,
93}
94
95#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
96impl TransactionAccountViewMut<'_> {
97    fn data_mut(&mut self) -> &mut Vec<u8> {
98        Arc::make_mut(&mut self.private_fields.payload)
99    }
100
101    pub(crate) fn raw_mut_data_slice(&mut self) -> *mut [u8] {
102        &raw mut self.data_mut()[..]
103    }
104
105    pub(crate) fn resize(&mut self, new_len: usize, value: u8) {
106        self.data_mut().resize(new_len, value);
107        self.abi_account.payload.set_len(new_len as u64);
108    }
109
110    #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
111    pub(crate) fn set_data_from_slice(&mut self, new_data: &[u8]) {
112        // If the buffer isn't shared, we're going to memcpy in place.
113        let Some(data) = Arc::get_mut(&mut self.private_fields.payload) else {
114            // If the buffer is shared, the cheapest thing to do is to clone the
115            // incoming slice and replace the buffer.
116            self.private_fields.payload = Arc::new(new_data.to_vec());
117            self.abi_account.payload.set_len(new_data.len() as u64);
118            return;
119        };
120
121        let new_len = new_data.len();
122
123        // Reserve additional capacity if needed. Here we make the assumption
124        // that growing the current buffer is cheaper than doing a whole new
125        // allocation to make `new_data` owned.
126        //
127        // This assumption holds true during CPI, especially when the account
128        // size doesn't change but the account is only changed in place. And
129        // it's also true when the account is grown by a small margin (the
130        // realloc limit is quite low), in which case the allocator can just
131        // update the allocation metadata without moving.
132        //
133        // Shrinking and copying in place is always faster than making
134        // `new_data` owned, since shrinking boils down to updating the Vec's
135        // length.
136
137        data.reserve(new_len.saturating_sub(data.len()));
138
139        // Safety:
140        // We just reserved enough capacity. We set data::len to 0 to avoid
141        // possible UB on panic (dropping uninitialized elements), do the copy,
142        // finally set the new length once everything is initialized.
143        unsafe {
144            data.set_len(0);
145            ptr::copy_nonoverlapping(new_data.as_ptr(), data.as_mut_ptr(), new_len);
146            data.set_len(new_len);
147            self.abi_account.payload.set_len(new_len as u64);
148        };
149    }
150
151    pub(crate) fn extend_from_slice(&mut self, data: &[u8]) {
152        self.data_mut().extend_from_slice(data);
153        self.abi_account
154            .payload
155            .set_len(self.private_fields.payload_len() as u64);
156    }
157
158    pub(crate) fn reserve(&mut self, additional: usize) {
159        if let Some(data) = Arc::get_mut(&mut self.private_fields.payload) {
160            data.reserve(additional)
161        } else {
162            let mut data =
163                Vec::with_capacity(self.private_fields.payload_len().saturating_add(additional));
164            data.extend_from_slice(self.private_fields.payload.as_slice());
165            self.private_fields.payload = Arc::new(data);
166        }
167    }
168
169    #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
170    pub(crate) fn is_shared(&self) -> bool {
171        Arc::strong_count(&self.private_fields.payload) > 1
172    }
173}
174
175#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
176impl ReadableAccount for TransactionAccountViewMut<'_> {
177    fn lamports(&self) -> u64 {
178        self.abi_account.lamports
179    }
180
181    fn data(&self) -> &[u8] {
182        self.private_fields.payload.as_slice()
183    }
184
185    fn owner(&self) -> &Pubkey {
186        &self.abi_account.owner
187    }
188
189    fn executable(&self) -> bool {
190        self.private_fields.executable
191    }
192
193    fn rent_epoch(&self) -> u64 {
194        self.private_fields.rent_epoch
195    }
196}
197
198#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
199impl WritableAccount for TransactionAccountViewMut<'_> {
200    fn set_lamports(&mut self, lamports: u64) {
201        self.abi_account.lamports = lamports;
202    }
203
204    fn data_as_mut_slice(&mut self) -> &mut [u8] {
205        Arc::make_mut(&mut self.private_fields.payload).as_mut_slice()
206    }
207
208    fn set_owner(&mut self, owner: Pubkey) {
209        self.abi_account.owner = owner;
210    }
211
212    fn copy_into_owner_from_slice(&mut self, source: &[u8]) {
213        self.abi_account.owner.as_mut().copy_from_slice(source);
214    }
215
216    fn set_executable(&mut self, executable: bool) {
217        self.private_fields.executable = executable;
218    }
219
220    fn set_rent_epoch(&mut self, epoch: u64) {
221        self.private_fields.rent_epoch = epoch;
222    }
223}
224
225/// An account key and the matching account
226#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
227pub type KeyedAccountSharedData = (Pubkey, AccountSharedData);
228#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
229pub(crate) type DeconstructedTransactionAccounts =
230    (Vec<KeyedAccountSharedData>, Box<[Cell<bool>]>, Cell<i64>);
231
232#[derive(Debug)]
233#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
234pub struct TransactionAccounts {
235    shared_account_fields: Box<[UnsafeCell<AccountSharedFields>]>,
236    private_account_fields: Box<[UnsafeCell<AccountPrivateFields>]>,
237    borrow_counters: Box<[BorrowCounter]>,
238    touched_flags: Box<[Cell<bool>]>,
239    resize_delta: Cell<i64>,
240    lamports_delta: Cell<i128>,
241}
242
243#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
244impl TransactionAccounts {
245    pub(crate) fn new(accounts: Vec<KeyedAccountSharedData>) -> TransactionAccounts {
246        let touched_flags = vec![Cell::new(false); accounts.len()].into_boxed_slice();
247        let borrow_counters = vec![BorrowCounter::default(); accounts.len()].into_boxed_slice();
248        let (shared_accounts, private_fields) = accounts
249            .into_iter()
250            .enumerate()
251            .map(|(idx, item)| {
252                (
253                    UnsafeCell::new(AccountSharedFields {
254                        key: item.0,
255                        owner: *item.1.owner(),
256                        lamports: item.1.lamports(),
257                        payload: VmSlice::new(
258                            GUEST_ACCOUNT_PAYLOAD_BASE_ADDRESS
259                                .saturating_add(GUEST_REGION_SIZE.saturating_mul(idx as u64)),
260                            item.1.data().len() as u64,
261                        ),
262                    }),
263                    UnsafeCell::new(AccountPrivateFields {
264                        rent_epoch: item.1.rent_epoch(),
265                        executable: item.1.executable(),
266                        payload: item.1.data_clone(),
267                    }),
268                )
269            })
270            .collect::<(
271                Vec<UnsafeCell<AccountSharedFields>>,
272                Vec<UnsafeCell<AccountPrivateFields>>,
273            )>();
274
275        TransactionAccounts {
276            shared_account_fields: shared_accounts.into_boxed_slice(),
277            private_account_fields: private_fields.into_boxed_slice(),
278            borrow_counters,
279            touched_flags,
280            resize_delta: Cell::new(0),
281            lamports_delta: Cell::new(0),
282        }
283    }
284
285    pub(crate) fn len(&self) -> usize {
286        self.shared_account_fields.len()
287    }
288
289    pub fn touch(&self, index: IndexOfAccount) -> Result<(), InstructionError> {
290        self.touched_flags
291            .get(index as usize)
292            .ok_or(InstructionError::MissingAccount)?
293            .set(true);
294        Ok(())
295    }
296
297    pub(crate) fn update_accounts_resize_delta(
298        &self,
299        old_len: usize,
300        new_len: usize,
301    ) -> Result<(), InstructionError> {
302        let accounts_resize_delta = self.resize_delta.get();
303        self.resize_delta.set(
304            accounts_resize_delta.saturating_add((new_len as i64).saturating_sub(old_len as i64)),
305        );
306        Ok(())
307    }
308
309    pub(crate) fn can_data_be_resized(
310        &self,
311        old_len: usize,
312        new_len: usize,
313    ) -> Result<(), InstructionError> {
314        // The new length can not exceed the maximum permitted length
315        if new_len > MAX_ACCOUNT_DATA_LEN as usize {
316            return Err(InstructionError::InvalidRealloc);
317        }
318        // The resize can not exceed the per-transaction maximum
319        let length_delta = (new_len as i64).saturating_sub(old_len as i64);
320        if self.resize_delta.get().saturating_add(length_delta)
321            > MAX_ACCOUNT_DATA_GROWTH_PER_TRANSACTION
322        {
323            return Err(InstructionError::MaxAccountsDataAllocationsExceeded);
324        }
325        Ok(())
326    }
327
328    #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
329    pub(crate) fn try_borrow_mut(
330        &self,
331        index: IndexOfAccount,
332    ) -> Result<AccountRefMut<'_>, InstructionError> {
333        let borrow_counter = self
334            .borrow_counters
335            .get(index as usize)
336            .ok_or(InstructionError::MissingAccount)?;
337        borrow_counter.try_borrow_mut()?;
338
339        // SAFETY: The borrow counter guarantees this is the only mutable borrow of this account.
340        // The unwrap is safe because accounts.len() == borrow_counters.len(), so the missing
341        // account error should have been returned above.
342        let svm_account = unsafe {
343            &mut *self
344                .shared_account_fields
345                .get(index as usize)
346                .unwrap()
347                .get()
348        };
349
350        let private_fields = unsafe {
351            &mut *self
352                .private_account_fields
353                .get(index as usize)
354                .unwrap()
355                .get()
356        };
357
358        let account = TransactionAccountViewMut {
359            abi_account: svm_account,
360            private_fields,
361        };
362
363        Ok(AccountRefMut {
364            account,
365            borrow_counter,
366        })
367    }
368
369    pub fn try_borrow(&self, index: IndexOfAccount) -> Result<AccountRef<'_>, InstructionError> {
370        let borrow_counter = self
371            .borrow_counters
372            .get(index as usize)
373            .ok_or(InstructionError::MissingAccount)?;
374        borrow_counter.try_borrow()?;
375
376        // SAFETY: The borrow counter guarantees there are no mutable borrow of this account.
377        // The unwrap is safe because accounts.len() == borrow_counters.len(), so the missing
378        // account error should have been returned above.
379        let svm_account = unsafe {
380            &*self
381                .shared_account_fields
382                .get(index as usize)
383                .unwrap()
384                .get()
385        };
386
387        let private_fields = unsafe {
388            &*self
389                .private_account_fields
390                .get(index as usize)
391                .unwrap()
392                .get()
393        };
394
395        let account = TransactionAccountView {
396            abi_account: svm_account,
397            private_fields,
398        };
399
400        Ok(AccountRef {
401            account,
402            borrow_counter,
403        })
404    }
405
406    pub(crate) fn add_lamports_delta(&self, balance: i128) -> Result<(), InstructionError> {
407        let delta = self.lamports_delta.get();
408        self.lamports_delta.set(
409            delta
410                .checked_add(balance)
411                .ok_or(InstructionError::ArithmeticOverflow)?,
412        );
413        Ok(())
414    }
415
416    pub(crate) fn get_lamports_delta(&self) -> i128 {
417        self.lamports_delta.get()
418    }
419
420    fn deconstruct_into_keyed_account_shared_data(&mut self) -> Vec<KeyedAccountSharedData> {
421        let shared_account_fields = std::mem::take(&mut self.shared_account_fields);
422        let private_account_fields = std::mem::take(&mut self.private_account_fields);
423        shared_account_fields
424            .into_iter()
425            .zip(private_account_fields)
426            .map(|(shared_fields_cell, private_fields_cell)| {
427                let shared_fields = shared_fields_cell.into_inner();
428                let private_fields = private_fields_cell.into_inner();
429                (
430                    shared_fields.key,
431                    AccountSharedData::create_from_existing_shared_data(
432                        shared_fields.lamports,
433                        private_fields.payload.clone(),
434                        shared_fields.owner,
435                        private_fields.executable,
436                        private_fields.rent_epoch,
437                    ),
438                )
439            })
440            .collect()
441    }
442
443    pub(crate) fn deconstruct_into_account_shared_data(&mut self) -> Vec<AccountSharedData> {
444        let shared_account_fields = std::mem::take(&mut self.shared_account_fields);
445        let private_account_fields = std::mem::take(&mut self.private_account_fields);
446        shared_account_fields
447            .into_iter()
448            .zip(private_account_fields)
449            .map(|(shared_fields_cell, private_fields_cell)| {
450                let shared_fields = shared_fields_cell.into_inner();
451                let private_fields = private_fields_cell.into_inner();
452                AccountSharedData::create_from_existing_shared_data(
453                    shared_fields.lamports,
454                    private_fields.payload.clone(),
455                    shared_fields.owner,
456                    private_fields.executable,
457                    private_fields.rent_epoch,
458                )
459            })
460            .collect()
461    }
462
463    pub(crate) fn take(mut self) -> DeconstructedTransactionAccounts {
464        let shared_data = self.deconstruct_into_keyed_account_shared_data();
465        (shared_data, self.touched_flags, self.resize_delta)
466    }
467
468    pub fn resize_delta(&self) -> i64 {
469        self.resize_delta.get()
470    }
471
472    pub(crate) fn account_key(&self, index: IndexOfAccount) -> Option<&Pubkey> {
473        // SAFETY: We never modify an account key, so returning a reference to it is safe.
474        unsafe {
475            self.shared_account_fields
476                .get(index as usize)
477                .map(|acc| &(*acc.get()).key)
478        }
479    }
480
481    pub(crate) fn account_keys_iter(&self) -> impl Iterator<Item = &Pubkey> {
482        // SAFETY: We never modify account keys, so returning an immutable reference to them is safe.
483        unsafe {
484            self.shared_account_fields
485                .iter()
486                .map(|item| &(*item.get()).key)
487        }
488    }
489}
490
491#[derive(Default, Debug, Clone)]
492#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
493struct BorrowCounter {
494    counter: Cell<i8>,
495}
496
497#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
498impl BorrowCounter {
499    #[inline]
500    fn is_writing(&self) -> bool {
501        self.counter.get() < 0
502    }
503
504    #[inline]
505    fn is_reading(&self) -> bool {
506        self.counter.get() > 0
507    }
508
509    #[inline]
510    fn try_borrow(&self) -> Result<(), InstructionError> {
511        if self.is_writing() {
512            return Err(InstructionError::AccountBorrowFailed);
513        }
514
515        if let Some(counter) = self.counter.get().checked_add(1) {
516            self.counter.set(counter);
517            return Ok(());
518        }
519
520        Err(InstructionError::AccountBorrowFailed)
521    }
522
523    #[inline]
524    fn try_borrow_mut(&self) -> Result<(), InstructionError> {
525        if self.is_writing() || self.is_reading() {
526            return Err(InstructionError::AccountBorrowFailed);
527        }
528
529        self.counter.set(self.counter.get().saturating_sub(1));
530
531        Ok(())
532    }
533
534    #[inline]
535    fn release_borrow(&self) {
536        self.counter.set(self.counter.get().saturating_sub(1));
537    }
538
539    #[inline]
540    fn release_borrow_mut(&self) {
541        self.counter.set(self.counter.get().saturating_add(1));
542    }
543}
544
545#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
546pub struct AccountRef<'a> {
547    account: TransactionAccountView<'a>,
548    borrow_counter: &'a BorrowCounter,
549}
550
551#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
552impl Drop for AccountRef<'_> {
553    fn drop(&mut self) {
554        self.borrow_counter.release_borrow();
555    }
556}
557
558#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
559impl<'a> Deref for AccountRef<'a> {
560    type Target = TransactionAccountView<'a>;
561    fn deref(&self) -> &Self::Target {
562        &self.account
563    }
564}
565
566#[derive(Debug)]
567#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
568pub struct AccountRefMut<'a> {
569    account: TransactionAccountViewMut<'a>,
570    borrow_counter: &'a BorrowCounter,
571}
572
573#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
574impl Drop for AccountRefMut<'_> {
575    fn drop(&mut self) {
576        self.account
577            .abi_account
578            .payload
579            .set_len(self.account.private_fields.payload_len() as u64);
580        self.borrow_counter.release_borrow_mut();
581    }
582}
583
584#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
585impl<'a> Deref for AccountRefMut<'a> {
586    type Target = TransactionAccountViewMut<'a>;
587    fn deref(&self) -> &Self::Target {
588        &self.account
589    }
590}
591
592#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
593impl DerefMut for AccountRefMut<'_> {
594    fn deref_mut(&mut self) -> &mut Self::Target {
595        &mut self.account
596    }
597}
598
599#[cfg(all(test, not(target_arch = "sbf"), not(target_arch = "bpf")))]
600mod tests {
601    use {
602        crate::transaction_accounts::TransactionAccounts, solana_account::AccountSharedData,
603        solana_instruction::error::InstructionError, solana_pubkey::Pubkey,
604    };
605
606    #[test]
607    fn test_missing_account() {
608        let accounts = vec![
609            (
610                Pubkey::new_unique(),
611                AccountSharedData::new(2, 1, &Pubkey::new_unique()),
612            ),
613            (
614                Pubkey::new_unique(),
615                AccountSharedData::new(2, 1, &Pubkey::new_unique()),
616            ),
617        ];
618
619        let tx_accounts = TransactionAccounts::new(accounts);
620
621        let res = tx_accounts.try_borrow(3);
622        assert_eq!(res.err(), Some(InstructionError::MissingAccount));
623
624        let res = tx_accounts.try_borrow_mut(3);
625        assert_eq!(res.err(), Some(InstructionError::MissingAccount));
626    }
627
628    #[test]
629    fn test_invalid_borrow() {
630        let accounts = vec![
631            (
632                Pubkey::new_unique(),
633                AccountSharedData::new(2, 1, &Pubkey::new_unique()),
634            ),
635            (
636                Pubkey::new_unique(),
637                AccountSharedData::new(2, 1, &Pubkey::new_unique()),
638            ),
639        ];
640
641        let tx_accounts = TransactionAccounts::new(accounts);
642
643        // Two immutable borrows are valid
644        {
645            let acc_1 = tx_accounts.try_borrow(0);
646            assert!(acc_1.is_ok());
647
648            let acc_2 = tx_accounts.try_borrow(1);
649            assert!(acc_2.is_ok());
650
651            let acc_1_new = tx_accounts.try_borrow(0);
652            assert!(acc_1_new.is_ok());
653
654            assert_eq!(acc_1.unwrap().account, acc_1_new.unwrap().account);
655        }
656
657        // Two mutable borrows are invalid
658        {
659            let acc_1 = tx_accounts.try_borrow_mut(0);
660            assert!(acc_1.is_ok());
661
662            let acc_2 = tx_accounts.try_borrow_mut(1);
663            assert!(acc_2.is_ok());
664
665            let acc_1_new = tx_accounts.try_borrow_mut(0);
666            assert_eq!(acc_1_new.err(), Some(InstructionError::AccountBorrowFailed));
667        }
668
669        // Mutable after immutable must fail
670        {
671            let acc_1 = tx_accounts.try_borrow(0);
672            assert!(acc_1.is_ok());
673
674            let acc_2 = tx_accounts.try_borrow(1);
675            assert!(acc_2.is_ok());
676
677            let acc_1_new = tx_accounts.try_borrow_mut(0);
678            assert_eq!(acc_1_new.err(), Some(InstructionError::AccountBorrowFailed));
679        }
680
681        // Immutable after mutable must fail
682        {
683            let acc_1 = tx_accounts.try_borrow_mut(0);
684            assert!(acc_1.is_ok());
685
686            let acc_2 = tx_accounts.try_borrow_mut(1);
687            assert!(acc_2.is_ok());
688
689            let acc_1_new = tx_accounts.try_borrow(0);
690            assert_eq!(acc_1_new.err(), Some(InstructionError::AccountBorrowFailed));
691        }
692
693        // Different scopes are good
694        {
695            let acc_1 = tx_accounts.try_borrow_mut(0);
696            assert!(acc_1.is_ok());
697        }
698
699        {
700            let acc_1 = tx_accounts.try_borrow_mut(0);
701            assert!(acc_1.is_ok());
702        }
703    }
704
705    #[test]
706    fn too_many_borrows() {
707        let accounts = vec![
708            (
709                Pubkey::new_unique(),
710                AccountSharedData::new(2, 1, &Pubkey::new_unique()),
711            ),
712            (
713                Pubkey::new_unique(),
714                AccountSharedData::new(2, 1, &Pubkey::new_unique()),
715            ),
716        ];
717
718        let tx_accounts = TransactionAccounts::new(accounts);
719        let mut borrows = Vec::new();
720        for i in 0..129 {
721            let acc = tx_accounts.try_borrow(1);
722            if i < 127 {
723                assert!(acc.is_ok());
724                borrows.push(acc.unwrap());
725            } else {
726                assert_eq!(acc.err(), Some(InstructionError::AccountBorrowFailed));
727            }
728        }
729    }
730}