@@ -39,13 +39,14 @@ pub struct Context<'a, T: Bumps> {
3939 /// only surface during the trailing walk).
4040 mut_mask : & ' static [ u64 ; 4 ] ,
4141
42- /// Cache of the parsed remaining accounts. Populated lazily on the
43- /// first successful call to [`Self::remaining_accounts`]; subsequent
44- /// calls return a clone of the cached vec. On failure the cache is
45- /// left unset so a retry re-executes the walk (a program is unlikely
46- /// to retry past a `ConstraintDuplicateMutableAccount` error, but we
47- /// avoid caching stale state regardless).
48- remaining_cache : Option < alloc:: vec:: Vec < AccountView > > ,
42+ /// Cached outcome of the remaining-accounts walk. `None` until the
43+ /// first call to [`Self::remaining_accounts`]; then stores whichever
44+ /// `Result` that walk produced. Subsequent calls replay the cache
45+ /// (clone of the vec on success, clone of the error on failure) so
46+ /// the cursor is advanced exactly once per instruction — retrying
47+ /// after an error must not call `cursor.next()` again, which would
48+ /// walk past the remaining region.
49+ remaining_cache : Option < Result < alloc:: vec:: Vec < AccountView > , ProgramError > > ,
4950}
5051
5152impl < ' a , T : Bumps > Context < ' a , T > {
@@ -70,9 +71,12 @@ impl<'a, T: Bumps> Context<'a, T> {
7071 }
7172
7273 /// Returns trailing accounts beyond the declared `T` fields as an
73- /// owned `Vec<AccountView>`. First call walks the cursor and caches;
74- /// subsequent calls clone the cache. Owned vec avoids borrow conflicts
75- /// with `self.accounts` / `self.bumps`.
74+ /// owned `Vec<AccountView>`. First call walks the cursor and caches
75+ /// the resulting `Result`; subsequent calls replay the cache (clone
76+ /// of the vec on success, clone of the error on failure). Caching
77+ /// the error is important: the walk advances an unsafe cursor, and
78+ /// a handler that calls this again after an error must not trigger
79+ /// another `cursor.next()` loop.
7680 ///
7781 /// After each cursor advance, re-tests the cursor's duplicate bitvec
7882 /// against `T::MUT_MASK`. If a trailing account's dup index resolves
@@ -88,27 +92,29 @@ impl<'a, T: Bumps> Context<'a, T> {
8892 /// the trailing slot as a dup of that declared mut account.
8993 pub fn remaining_accounts ( & mut self ) -> Result < alloc:: vec:: Vec < AccountView > , ProgramError > {
9094 if self . remaining_cache . is_none ( ) {
91- let mut v = alloc:: vec:: Vec :: with_capacity ( self . remaining_num as usize ) ;
92- for _ in 0 ..self . remaining_num {
93- // SAFETY: cursor is positioned at the start of the
94- // remaining region and `remaining_num` is the exact
95- // number of accounts to walk.
96- v. push ( unsafe { self . cursor . next ( ) } ) ;
97- // If this advance materialized a dup whose earlier
98- // slot is a declared mut account, reject. `duplicates`
99- // is `Some` iff at least one dup has ever been seen
100- // (possibly during the `HEADER_SIZE` walk — but that
101- // case is already handled in `run_handler`, so any
102- // overlap here means a trailing account caused it).
103- if let Some ( dups) = self . cursor . duplicates ( ) {
104- if dups. intersects ( self . mut_mask ) {
105- return Err ( crate :: ErrorCode :: ConstraintDuplicateMutableAccount . into ( ) ) ;
106- }
95+ self . remaining_cache = Some ( self . walk_remaining ( ) ) ;
96+ }
97+ match self . remaining_cache . as_ref ( ) . unwrap ( ) {
98+ Ok ( v) => Ok ( v. clone ( ) ) ,
99+ Err ( e) => Err ( e. clone ( ) ) ,
100+ }
101+ }
102+
103+ fn walk_remaining ( & mut self ) -> Result < alloc:: vec:: Vec < AccountView > , ProgramError > {
104+ let mut v = alloc:: vec:: Vec :: with_capacity ( self . remaining_num as usize ) ;
105+ for _ in 0 ..self . remaining_num {
106+ // SAFETY: cursor is positioned at the start of the remaining
107+ // region and `remaining_num` is the exact number of accounts
108+ // to walk. Walking stops on the first mut-alias error so we
109+ // never advance past the remaining region.
110+ v. push ( unsafe { self . cursor . next ( ) } ) ;
111+ if let Some ( dups) = self . cursor . duplicates ( ) {
112+ if dups. intersects ( self . mut_mask ) {
113+ return Err ( crate :: ErrorCode :: ConstraintDuplicateMutableAccount . into ( ) ) ;
107114 }
108115 }
109- self . remaining_cache = Some ( v) ;
110116 }
111- Ok ( self . remaining_cache . as_ref ( ) . unwrap ( ) . clone ( ) )
117+ Ok ( v )
112118 }
113119}
114120
0 commit comments