33// Copyright © 2017 Trust Wallet. 
44
55use  crate :: address:: SolanaAddress ; 
6+ use  crate :: instruction:: AccountMeta ; 
67use  crate :: modules:: compiled_keys:: try_into_u8; 
78use  crate :: transaction:: v0:: MessageAddressTableLookup ; 
89use  crate :: transaction:: { CompiledInstruction ,  MessageHeader } ; 
@@ -11,6 +12,59 @@ use tw_coin_entry::error::prelude::*;
1112use  tw_memory:: Data ; 
1213
1314pub  trait  InsertInstruction  { 
15+     /// Pushes an instruction 
16+ fn  push_instruction ( 
17+         & mut  self , 
18+         program_id :  SolanaAddress , 
19+         accounts :  Vec < AccountMeta > , 
20+         data :  Data , 
21+     )  -> SigningResult < ( ) >  { 
22+         let  insert_at = self . instructions_mut ( ) . len ( ) ; 
23+         self . insert_instruction ( insert_at,  program_id,  accounts,  data) 
24+     } 
25+ 
26+     /// Inserts an instruction at the given `insert_at` index. 
27+ fn  insert_instruction ( 
28+         & mut  self , 
29+         insert_at :  usize , 
30+         program_id :  SolanaAddress , 
31+         accounts :  Vec < AccountMeta > , 
32+         data :  Data , 
33+     )  -> SigningResult < ( ) >  { 
34+         if  insert_at > self . instructions_mut ( ) . len ( )  { 
35+             return  SigningError :: err ( SigningErrorType :: Error_internal ) 
36+             . context ( format ! ( "Unable to add '{program_id}' instruction at the '{insert_at}' index. Number of existing instructions: {}" ,  self . instructions_mut( ) . len( ) ) ) ; 
37+         } 
38+ 
39+         // Step 1 - add the `account` in the accounts list. 
40+         let  accounts:  Vec < u8 >  = accounts
41+             . iter ( ) 
42+             . map ( |account_meta| self . push_account ( account_meta) ) 
43+             . collect :: < Result < Vec < u8 > ,  _ > > ( ) ?; 
44+ 
45+         // Step 2 - find or add the `program_id` in the accounts list. 
46+         let  program_id_index = match  self 
47+             . account_keys_mut ( ) 
48+             . iter ( ) 
49+             . position ( |acc| * acc == program_id) 
50+         { 
51+             Some ( pos)  => try_into_u8 ( pos) ?, 
52+             None  => self . push_readonly_unsigned_account ( program_id) ?, 
53+         } ; 
54+ 
55+         // Step 3 - Create a `CompiledInstruction` based on the `program_id` index and instruction `accounts` and `data`. 
56+         let  new_compiled_ix = CompiledInstruction  { 
57+             program_id_index, 
58+             accounts, 
59+             data, 
60+         } ; 
61+ 
62+         // Step 4 - Insert the created instruction at the given `insert_at` index. 
63+         self . instructions_mut ( ) . insert ( insert_at,  new_compiled_ix) ; 
64+ 
65+         Ok ( ( ) ) 
66+     } 
67+ 
1468    /// Pushes a simple instruction that doesn't have accounts. 
1569fn  push_simple_instruction ( 
1670        & mut  self , 
@@ -56,6 +110,107 @@ pub trait InsertInstruction {
56110        Ok ( ( ) ) 
57111    } 
58112
113+     /// Pushes an account to the message. 
114+ /// If the account already exists, it must match the `is_signer` and `is_writable` attributes 
115+ /// Returns the index of the account in the account list. 
116+ fn  push_account ( & mut  self ,  account :  & AccountMeta )  -> SigningResult < u8 >  { 
117+         // The layout of the account keys is as follows: 
118+         // +-------------------------------------+ 
119+         // | Writable and required signature     |                                     \ 
120+         // +-------------------------------------+                                      |->  num_required_signatures 
121+         // | Readonly and required signature     | --> num_readonly_signed_accounts    / 
122+         // +-------------------------------------+ 
123+         // | Writable and not required signature | 
124+         // +-------------------------------------+ 
125+         // | Readonly and not required signature | --> num_readonly_unsigned_accounts 
126+         // +-------------------------------------+ 
127+ 
128+         // Check if the account already exists in `account_keys`, 
129+         // if it does, validate `is_signer` and `is_writable` match 
130+         if  let  Some ( existing_index)  = self 
131+             . account_keys_mut ( ) 
132+             . iter ( ) 
133+             . position ( |key| * key == account. pubkey ) 
134+         { 
135+             let  is_signer =
136+                 existing_index < self . message_header_mut ( ) . num_required_signatures  as  usize ; 
137+ 
138+             let  is_writable = if  is_signer { 
139+                 existing_index
140+                     < ( self . message_header_mut ( ) . num_required_signatures 
141+                         - self . message_header_mut ( ) . num_readonly_signed_accounts ) 
142+                         as  usize 
143+             }  else  { 
144+                 existing_index
145+                     < ( self . account_keys_mut ( ) . len ( ) 
146+                         - self . message_header_mut ( ) . num_readonly_unsigned_accounts  as  usize ) 
147+             } ; 
148+ 
149+             if  account. is_signer  != is_signer { 
150+                 return  SigningError :: err ( SigningErrorType :: Error_internal ) . context ( 
151+                     "Account already exists but the `is_signer` attribute does not match" , 
152+                 ) ; 
153+             } 
154+             if  account. is_writable  != is_writable { 
155+                 return  SigningError :: err ( SigningErrorType :: Error_internal ) . context ( 
156+                     "Account already exists but the `is_writable` attribute does not match" , 
157+                 ) ; 
158+             } 
159+             // Return the existing index if validation passes 
160+             return  try_into_u8 ( existing_index) ; 
161+         } 
162+ 
163+         // Determine the insertion position based on is_signer and is_writable 
164+         let  insert_at = match  ( account. is_signer ,  account. is_writable )  { 
165+             ( true ,  true )  => { 
166+                 self . message_header_mut ( ) . num_required_signatures  += 1 ; 
167+                 // The account is added at the end of the writable and signer accounts 
168+                 ( self . message_header_mut ( ) . num_required_signatures 
169+                     - self . message_header_mut ( ) . num_readonly_signed_accounts ) 
170+                     as  usize 
171+                     - 1 
172+             } , 
173+             ( true ,  false )  => { 
174+                 self . message_header_mut ( ) . num_required_signatures  += 1 ; 
175+                 self . message_header_mut ( ) . num_readonly_signed_accounts  += 1 ; 
176+                 // The account is added at the end of the read-only and signer accounts 
177+                 self . message_header_mut ( ) . num_required_signatures  as  usize  - 1 
178+             } , 
179+             ( false ,  true )  => { 
180+                 // The account is added at the end of the writable and non-signer accounts 
181+                 self . account_keys_mut ( ) . len ( ) 
182+                     - self . message_header_mut ( ) . num_readonly_unsigned_accounts  as  usize 
183+             } , 
184+             ( false ,  false )  => { 
185+                 self . message_header_mut ( ) . num_readonly_unsigned_accounts  += 1 ; 
186+                 // The account is added at the end of the list 
187+                 self . account_keys_mut ( ) . len ( ) 
188+             } , 
189+         } ; 
190+ 
191+         // Insert the account at the determined position 
192+         self . account_keys_mut ( ) . insert ( insert_at,  account. pubkey ) ; 
193+ 
194+         let  account_added_at = try_into_u8 ( insert_at) ?; 
195+ 
196+         // Update program ID and account indexes if the new account was added before its position 
197+         let  instructions = self . instructions_mut ( ) ; 
198+         instructions. iter_mut ( ) . for_each ( |ix| { 
199+             // Update program ID index 
200+             if  ix. program_id_index  >= account_added_at { 
201+                 ix. program_id_index  += 1 ; 
202+             } 
203+ 
204+             // Update account indexes 
205+             ix. accounts 
206+                 . iter_mut ( ) 
207+                 . filter ( |ix_account_id| * * ix_account_id >= account_added_at) 
208+                 . for_each ( |ix_account_id| * ix_account_id += 1 ) ; 
209+         } ) ; 
210+ 
211+         Ok ( account_added_at) 
212+     } 
213+ 
59214    fn  push_readonly_unsigned_account ( & mut  self ,  account :  SolanaAddress )  -> SigningResult < u8 >  { 
60215        debug_assert ! ( 
61216            !self . account_keys_mut( ) . contains( & account) , 
0 commit comments