3
3
// Copyright © 2017 Trust Wallet.
4
4
5
5
use crate :: address:: SolanaAddress ;
6
+ use crate :: instruction:: AccountMeta ;
6
7
use crate :: modules:: compiled_keys:: try_into_u8;
7
8
use crate :: transaction:: v0:: MessageAddressTableLookup ;
8
9
use crate :: transaction:: { CompiledInstruction , MessageHeader } ;
@@ -11,6 +12,59 @@ use tw_coin_entry::error::prelude::*;
11
12
use tw_memory:: Data ;
12
13
13
14
pub 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
+
14
68
/// Pushes a simple instruction that doesn't have accounts.
15
69
fn push_simple_instruction (
16
70
& mut self ,
@@ -56,6 +110,107 @@ pub trait InsertInstruction {
56
110
Ok ( ( ) )
57
111
}
58
112
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
+
59
214
fn push_readonly_unsigned_account ( & mut self , account : SolanaAddress ) -> SigningResult < u8 > {
60
215
debug_assert ! (
61
216
!self . account_keys_mut( ) . contains( & account) ,
0 commit comments