Line data Source code
1 : /* This file contains all the logic that is common to both the C and Rust
2 : CPI syscalls (sol_invoke_signed_{rust/c}). As such, all of the functions in
3 : here are templated and will be instantiated for both the C and Rust CPI ABIs.
4 :
5 : The only difference between the C and Rust CPI syscalls is the ABI data layout
6 : of the parameters to these calls - all the logic is identical. As such, we have
7 : defined a series of macros to abstract away the ABI differences from the CPI implementation.
8 :
9 : The entry-point for these syscalls is VM_SYSCALL_CPI_ENTRYPOINT.
10 :
11 : Note that the code for these syscalls could be simplified somewhat, but we have opted to keep
12 : it as close to the Solana code as possible to make it easier to audit that we execute equivalently.
13 : Most of the top-level functions in this file correspond directly to functions in the Solana codebase
14 : and links to the source have been provided.
15 : */
16 :
17 : /* fd_vm_syscall_cpi_instruction_to_instr_{c/rust} takes the translated
18 : CPI ABI structures (instruction and account meta list), and uses these
19 : to populate a fd_instr_info_t struct. This struct can then be given to the
20 : FD runtime for execution.
21 :
22 : Parameters:
23 : - vm: handle to the vm
24 : - cpi_instr: instruction to execute laid out in the CPI ABI format (Rust or C)
25 : - cpi_acc_metas: list of account metas, again in the CPI ABI format
26 : - signers: derived signers for this CPI call
27 : - signers_cnt: length of the signers list
28 : - cpi_instr_data: instruction data in host address space
29 :
30 : TODO: return codes/errors?
31 : */
32 255 : #define VM_SYSCALL_CPI_INSTRUCTION_TO_INSTR_FUNC FD_EXPAND_THEN_CONCAT2(fd_vm_syscall_cpi_instruction_to_instr_, VM_SYSCALL_CPI_ABI)
33 : static int
34 : VM_SYSCALL_CPI_INSTRUCTION_TO_INSTR_FUNC( fd_vm_t * vm,
35 : VM_SYSCALL_CPI_INSTR_T const * cpi_instr,
36 : VM_SYSCALL_CPI_ACC_META_T const * cpi_acct_metas,
37 : fd_pubkey_t const * program_id,
38 : uchar const * cpi_instr_data,
39 255 : fd_instr_info_t * out_instr ) {
40 : /* fd_vm_prepare_instruction will handle the case where pubkey is missing */
41 255 : out_instr->program_id_pubkey = *program_id;
42 255 : out_instr->program_id = UCHAR_MAX;
43 :
44 : /* Find the index of the CPI instruction's program account in the transaction */
45 255 : fd_pubkey_t * txn_accs = vm->instr_ctx->txn_ctx->accounts;
46 7482 : for( ulong i=0UL; i < vm->instr_ctx->txn_ctx->accounts_cnt; i++ ) {
47 7482 : if( !memcmp( program_id, &txn_accs[i], sizeof( fd_pubkey_t ) ) ) {
48 255 : out_instr->program_id = (uchar)i;
49 255 : break;
50 255 : }
51 7482 : }
52 :
53 : /* Calculate summary information for the account list */
54 255 : ulong starting_lamports_h = 0UL;
55 255 : ulong starting_lamports_l = 0UL;
56 255 : uchar acc_idx_seen[256] = {0};
57 1470 : for( ulong i=0UL; i<VM_SYSCALL_CPI_INSTR_ACCS_LEN( cpi_instr ); i++ ) {
58 1215 : VM_SYSCALL_CPI_ACC_META_T const * cpi_acct_meta = &cpi_acct_metas[i];
59 1215 : uchar const * pubkey = VM_SYSCALL_CPI_ACC_META_PUBKEY( vm, cpi_acct_meta );
60 :
61 0 : int account_found = 0;
62 20580 : for( ulong j=0UL; j<vm->instr_ctx->txn_ctx->accounts_cnt; j++ ) {
63 20580 : if( !memcmp( pubkey, &txn_accs[j], sizeof(fd_pubkey_t) ) ) {
64 1215 : account_found = 1;
65 : /* TODO: error if flags are wrong */
66 1215 : memcpy( out_instr->acct_pubkeys[i].uc, pubkey, sizeof( fd_pubkey_t ) );
67 1215 : out_instr->acct_txn_idxs[i] = (uchar)j;
68 1215 : out_instr->acct_flags[i] = 0;
69 1215 : out_instr->borrowed_accounts[i] = &vm->instr_ctx->txn_ctx->borrowed_accounts[j];
70 1215 : out_instr->is_duplicate[i] = acc_idx_seen[j];
71 :
72 1215 : if( FD_LIKELY( !acc_idx_seen[j] ) ) {
73 : /* This is the first time seeing this account */
74 1212 : acc_idx_seen[j] = 1;
75 1212 : if( out_instr->borrowed_accounts[i]->const_meta ) {
76 : /* TODO: what if this account is borrowed as writable? */
77 1212 : fd_uwide_inc(
78 1212 : &starting_lamports_h, &starting_lamports_l,
79 1212 : starting_lamports_h, starting_lamports_l,
80 1212 : out_instr->borrowed_accounts[i]->const_meta->info.lamports );
81 1212 : }
82 1212 : }
83 :
84 : /* The parent flag(s) for is writable/signer is checked in
85 : fd_vm_prepare_instruction. Signer privilege is allowed iff the account
86 : is a signer in the caller or if it is a derived signer. */
87 1215 : if( VM_SYSCALL_CPI_ACC_META_IS_WRITABLE( cpi_acct_meta ) ) {
88 783 : out_instr->acct_flags[i] |= FD_INSTR_ACCT_FLAGS_IS_WRITABLE;
89 783 : }
90 :
91 1215 : if( VM_SYSCALL_CPI_ACC_META_IS_SIGNER( cpi_acct_meta ) ) {
92 264 : out_instr->acct_flags[i] |= FD_INSTR_ACCT_FLAGS_IS_SIGNER;
93 264 : }
94 :
95 1215 : break;
96 1215 : }
97 20580 : }
98 1215 : if( FD_UNLIKELY( !account_found ) ) {
99 0 : return FD_EXECUTOR_INSTR_ERR_MISSING_ACC;
100 0 : }
101 162 : }
102 :
103 255 : out_instr->data_sz = (ushort)VM_SYSCALL_CPI_INSTR_DATA_LEN( cpi_instr );
104 255 : out_instr->data = (uchar *)cpi_instr_data;
105 255 : out_instr->acct_cnt = (ushort)VM_SYSCALL_CPI_INSTR_ACCS_LEN( cpi_instr );
106 255 : out_instr->starting_lamports_h = starting_lamports_h;
107 255 : out_instr->starting_lamports_l = starting_lamports_l;
108 :
109 255 : return FD_VM_SUCCESS;
110 255 : }
111 :
112 : /*
113 : fd_vm_syscall_cpi_update_callee_acc_{rust/c} corresponds to solana_bpf_loader_program::syscalls::cpi::update_callee_account:
114 : https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/programs/bpf_loader/src/syscalls/cpi.rs#L1302
115 :
116 : (the copy of the account stored in the instruction context's
117 : borrowed accounts cache)
118 :
119 : This function should be called before the CPI instruction is executed. Its purpose is to
120 : update the callee account's view of the given account with any changes the caller may made
121 : to the account before the CPI instruction is executed.
122 :
123 : The callee's view of the account is the borrowed accounts cache, so to update the
124 : callee account we look up the account in the borrowed accounts cache and update it.
125 :
126 : Paramaters:
127 : - vm: pointer to the virtual machine handle
128 : - account_info: account info object
129 : - callee_acc_pubkey: pubkey of the account. this is used to look up the account in the borrowed accounts cache
130 : (TODO: this seems redundant? we can probably remove this, as the account_info contains the pubkey)
131 : */
132 : #define VM_SYCALL_CPI_UPDATE_CALLEE_ACC_FUNC FD_EXPAND_THEN_CONCAT2(fd_vm_syscall_cpi_update_callee_acc_, VM_SYSCALL_CPI_ABI)
133 : static int
134 : VM_SYCALL_CPI_UPDATE_CALLEE_ACC_FUNC( fd_vm_t * vm,
135 : VM_SYSCALL_CPI_ACC_INFO_T const * account_info,
136 1098 : uchar instr_acc_idx ) {
137 : /* Consume compute units for the account data access */
138 :
139 : /* FIXME: do we also need to consume the compute units if the account is not known? */
140 :
141 1098 : VM_SYSCALL_CPI_ACC_INFO_METADATA( vm, account_info, caller_acc_data );
142 :
143 : // FIXME: should this be FD_VM_CU_MEM_UPDATE? Changing this changes the CU behaviour from main
144 1098 : FD_VM_CU_UPDATE( vm, caller_acc_data_len / FD_VM_CPI_BYTES_PER_UNIT );
145 :
146 0 : fd_borrowed_account_t * callee_acc = NULL;
147 1098 : int err = fd_instr_borrowed_account_modify_idx(vm->instr_ctx, instr_acc_idx, 0, &callee_acc);
148 1098 : if( FD_UNLIKELY( err ) ) {
149 : /* No need to do anything if the account is missing from the borrowed accounts cache */
150 0 : return FD_VM_SUCCESS;
151 0 : }
152 :
153 1098 : if( FD_UNLIKELY( !callee_acc->meta ) ) {
154 : /* If the account is not modifiable, we can't change it (and it can't have been changed by the callee) */
155 171 : return FD_VM_SUCCESS;
156 171 : }
157 :
158 : /* Update the lamports. TODO: This should technically be a load, but it
159 : doesn't matter in this case. */
160 2781 : VM_SYSCALL_CPI_ACC_INFO_LAMPORTS( vm, account_info, caller_acc_lamports );
161 2781 : if( callee_acc->meta->info.lamports!=*caller_acc_lamports ) {
162 0 : err = fd_account_set_lamports( vm->instr_ctx, instr_acc_idx, *caller_acc_lamports );
163 0 : if( FD_UNLIKELY( err ) ) {
164 0 : return err;
165 0 : }
166 0 : }
167 :
168 927 : if( !vm->direct_mapping ) {
169 : /* Get the account data */
170 : /* Update the account data, if the account data can be changed */
171 : /* FIXME: double-check these permissions, especially the callee_acc_idx */
172 :
173 : /* Translate and get the account data */
174 2781 : uchar const * caller_acc_data = FD_VM_MEM_HADDR_LD( vm, caller_acc_data_vm_addr, sizeof(uchar), caller_acc_data_len );
175 :
176 927 : if( fd_account_can_data_be_resized( vm->instr_ctx, callee_acc->meta, caller_acc_data_len, &err ) &&
177 927 : fd_account_can_data_be_changed( vm->instr_ctx, instr_acc_idx, &err ) ) {
178 : /* We must ignore the errors here, as they are informational and do not mean the result is invalid. */
179 : /* TODO: not pass informational errors like this? */
180 :
181 45 : err = fd_account_set_data_from_slice( vm->instr_ctx, instr_acc_idx, caller_acc_data, caller_acc_data_len );
182 45 : if( FD_UNLIKELY( err ) ) {
183 0 : return err;
184 0 : }
185 882 : } else if( FD_UNLIKELY( caller_acc_data_len!=callee_acc->const_meta->dlen ||
186 882 : memcmp( callee_acc->const_data, caller_acc_data, caller_acc_data_len ) ) ) {
187 0 : return err;
188 0 : }
189 :
190 927 : uchar const * caller_acc_owner = FD_VM_MEM_HADDR_ST( vm, account_info->owner_addr, alignof(uchar), sizeof(fd_pubkey_t) );
191 927 : if( memcmp( callee_acc->meta->info.owner, caller_acc_owner, sizeof(fd_pubkey_t) ) ) {
192 0 : fd_memcpy( callee_acc->meta->info.owner, caller_acc_owner, sizeof(fd_pubkey_t) );
193 0 : }
194 :
195 927 : } else { /* Direct mapping enabled */
196 0 : ulong region_idx = vm->acc_region_metas[ instr_acc_idx ].region_idx;
197 0 : uint original_len = vm->acc_region_metas[ instr_acc_idx ].has_data_region ?
198 0 : vm->input_mem_regions[ region_idx ].region_sz : 0U;
199 0 : ulong prev_len = callee_acc->const_meta->dlen;
200 0 : ulong post_len = caller_acc_data_len;
201 :
202 0 : int err;
203 0 : if( fd_account_can_data_be_resized( vm->instr_ctx, callee_acc->meta, post_len, &err ) &&
204 0 : fd_account_can_data_be_changed( vm->instr_ctx, instr_acc_idx, &err ) ) {
205 :
206 0 : ulong realloc_bytes_used = fd_ulong_sat_sub( post_len, original_len );
207 :
208 0 : if( FD_UNLIKELY( vm->is_deprecated && realloc_bytes_used ) ) {
209 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_REALLOC;
210 0 : }
211 :
212 0 : err = fd_account_set_data_length( vm->instr_ctx, instr_acc_idx, post_len );
213 0 : if( FD_UNLIKELY( err ) ) {
214 0 : return err;
215 0 : }
216 :
217 :
218 0 : if( realloc_bytes_used ) {
219 : /* We need to get the relevant data slice. However, we know that the
220 : current length currently exceeds the original length for the account
221 : data. This means that all of the additional bytes must exist in the
222 : account data resizing region. As an invariant, original_len must be
223 : equal to the length of the account data region. This means we can
224 : smartly look up the right region and don't need to worry about
225 : multiple region access.We just need to load in the bytes from
226 : (original len, post_len]. */
227 0 : uchar const * realloc_data = FD_VM_MEM_HADDR_LD( vm, caller_acc_data_vm_addr+original_len, alignof(uchar), realloc_bytes_used );
228 :
229 0 : uchar * data = NULL;
230 0 : ulong dlen = 0UL;
231 0 : err = fd_account_get_data_mut( vm->instr_ctx, instr_acc_idx, &data, &dlen );
232 0 : if( FD_UNLIKELY( err ) ) {
233 0 : return err;
234 0 : }
235 0 : fd_memcpy( data+original_len, realloc_data, realloc_bytes_used );
236 0 : }
237 :
238 0 : } else if( FD_UNLIKELY( prev_len!=post_len ) ) {
239 0 : return err;
240 0 : }
241 0 : }
242 :
243 2781 : uchar const * caller_acc_owner = FD_VM_MEM_HADDR_LD( vm, account_info->owner_addr, alignof(uchar), sizeof(fd_pubkey_t) );
244 927 : if( FD_UNLIKELY( memcmp( callee_acc->meta->info.owner, caller_acc_owner, sizeof(fd_pubkey_t) ) ) ) {
245 0 : err = fd_account_set_owner( vm->instr_ctx, instr_acc_idx, (fd_pubkey_t*)caller_acc_owner );
246 0 : if( FD_UNLIKELY( err ) ) {
247 0 : return err;
248 0 : }
249 0 : }
250 :
251 927 : return FD_VM_SUCCESS;
252 2781 : }
253 :
254 : /*
255 : fd_vm_syscall_cpi_translate_and_update_accounts_ mirrors the behaviour of
256 : solana_bpf_loader_program::syscalls::cpi::translate_and_update_accounts:
257 : https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/programs/bpf_loader/src/syscalls/cpi.rs#L954-L1085
258 :
259 : It translates the caller accounts to the host address space, and then calls
260 : fd_vm_syscall_cpi_update_callee_acc to update the callee borrowed account with any changes
261 : the caller has made to the account during execution before this CPI call.
262 :
263 : It also populates the out_callee_indices and out_caller_indices arrays:
264 : - out_callee_indices: indices of the callee accounts in the transaction
265 : - out_caller_indices: indices of the caller accounts in the account_infos array
266 :
267 : Parameters:
268 : - vm: pointer to the virtual machine handle
269 : - instruction_accounts: array of instruction accounts
270 : - instruction_accounts_cnt: length of the instruction_accounts array
271 : - account_infos: array of account infos
272 : - account_infos_length: length of the account_infos array
273 :
274 : Populates the given out_callee_indices and out_caller_indices arrays:
275 : - out_callee_indices: indices of the callee accounts in the transaction
276 : - out_caller_indices: indices of the caller accounts in the account_infos array
277 : - out_len: length of the out_callee_indices and out_caller_indices arrays
278 : */
279 252 : #define VM_SYSCALL_CPI_TRANSLATE_AND_UPDATE_ACCOUNTS_FUNC FD_EXPAND_THEN_CONCAT2(fd_vm_syscall_cpi_translate_and_update_accounts_, VM_SYSCALL_CPI_ABI)
280 : static int
281 : VM_SYSCALL_CPI_TRANSLATE_AND_UPDATE_ACCOUNTS_FUNC(
282 : fd_vm_t * vm,
283 : fd_instruction_account_t const * instruction_accounts,
284 : ulong const instruction_accounts_cnt,
285 : VM_SYSCALL_CPI_ACC_INFO_T const * account_infos,
286 : ulong const account_infos_length,
287 : ulong * out_callee_indices,
288 : ulong * out_caller_indices,
289 252 : ulong * out_len ) {
290 :
291 1461 : for( ulong i=0UL; i<instruction_accounts_cnt; i++ ) {
292 1209 : if( i!=instruction_accounts[i].index_in_callee ) {
293 : /* Skip duplicate accounts */
294 3 : continue;
295 3 : }
296 :
297 : /* `fd_vm_prepare_instruction()` will always set up a valid index for `index_in_caller`, so we can access the borrowed account directly.
298 : A borrowed account will always have non-NULL meta (if the account doesn't exist, `fd_executor_setup_borrowed_accounts_for_txn()`
299 : will set its meta up) */
300 1206 : fd_borrowed_account_t const * acc_rec = vm->instr_ctx->instr->borrowed_accounts[instruction_accounts[i].index_in_caller];
301 1206 : fd_pubkey_t const * account_key = acc_rec->pubkey;
302 1206 : fd_account_meta_t const * acc_meta = acc_rec->const_meta;
303 :
304 : /* If the account is known and executable, we only need to consume the compute units.
305 : Executable accounts can't be modified, so we don't need to update the callee account. */
306 1206 : if( fd_account_is_executable( acc_meta ) ) {
307 : // FIXME: should this be FD_VM_CU_MEM_UPDATE? Changing this changes the CU behaviour from main (because of the base cost)
308 108 : FD_VM_CU_UPDATE( vm, acc_meta->dlen / FD_VM_CPI_BYTES_PER_UNIT );
309 0 : continue;
310 108 : }
311 :
312 : /* Find the indicies of the account in the caller and callee instructions */
313 1098 : uint found = 0;
314 6744 : for( ulong j=0; (j < account_infos_length) && !found; j++ ) {
315 :
316 : /* Look up the pubkey to see if it is the account we're looking for,
317 : error out if invalid address (implies bad account_infos and is also what Agave does).
318 : https://github.com/firedancer-io/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/programs/bpf_loader/src/syscalls/cpi.rs#L828-L832 */
319 16938 : fd_pubkey_t const * acct_addr = FD_VM_MEM_HADDR_LD(
320 16938 : vm, account_infos[j].pubkey_addr, alignof(uchar), sizeof(fd_pubkey_t) );
321 :
322 5646 : if( memcmp( account_key->uc, acct_addr->uc, sizeof(fd_pubkey_t) ) != 0 ) {
323 4548 : continue;
324 4548 : }
325 :
326 : /* Record the indicies of this account */
327 1098 : ulong index_in_caller = instruction_accounts[i].index_in_caller;
328 1098 : if (instruction_accounts[i].is_writable) {
329 768 : out_callee_indices[*out_len] = index_in_caller;
330 768 : out_caller_indices[*out_len] = j;
331 768 : (*out_len)++;
332 768 : }
333 1098 : found = 1;
334 :
335 1098 : if ( vm->direct_mapping ) {
336 : /* Check that the account info pointers given by the user correspond to the correct locations
337 : in the serialized account metadata
338 :
339 : https://github.com/anza-xyz/agave/blob/v2.1.7/programs/bpf_loader/src/syscalls/cpi.rs#L116 */
340 0 : fd_vm_acc_region_meta_t * acc_region_meta = &vm->acc_region_metas[index_in_caller];
341 0 : ulong expected_pubkey_vaddr = serialized_pubkey_vaddr( vm, acc_region_meta );
342 0 : if( FD_UNLIKELY( account_infos[j].pubkey_addr!=expected_pubkey_vaddr )) {
343 : /* Max msg_sz: 40 + 18 + 18 = 76 < 127 => we can use printf */
344 0 : fd_log_collector_printf_dangerous_max_127( vm->instr_ctx,
345 0 : "Invalid account info pointer `key': %#lx != %#lx", account_infos[j].pubkey_addr, expected_pubkey_vaddr );
346 0 : return FD_VM_SYSCALL_ERR_INVALID_POINTER;
347 0 : }
348 :
349 : /* https://github.com/anza-xyz/agave/blob/v2.1.7/programs/bpf_loader/src/syscalls/cpi.rs#L122 */
350 0 : ulong expected_owner_vaddr = serialized_owner_vaddr( vm, acc_region_meta );
351 0 : if( FD_UNLIKELY( account_infos[j].owner_addr!=expected_owner_vaddr )) {
352 : /* Max msg_sz: 42 + 18 + 18 = 78 < 127 => we can use printf */
353 0 : fd_log_collector_printf_dangerous_max_127( vm->instr_ctx,
354 0 : "Invalid account info pointer `owner': %#lx != %#lx", account_infos[j].owner_addr, expected_owner_vaddr );
355 0 : return FD_VM_SYSCALL_ERR_INVALID_POINTER;
356 0 : }
357 :
358 : /* Check that the account's lamports Rc<RefCell<T>> is not stored in the account. Because a refcell is
359 : only present if the Rust SDK is used, we only need to check this for the Rust SDK.
360 :
361 : https://github.com/anza-xyz/agave/blob/v2.1.7/programs/bpf_loader/src/syscalls/cpi.rs#L140 */
362 : #ifdef VM_SYSCALL_CPI_ACC_INFO_LAMPORTS_RC_REFCELL_VADDR
363 0 : VM_SYSCALL_CPI_ACC_INFO_LAMPORTS_RC_REFCELL_VADDR( vm, (account_infos + j), lamports_rc_vaddr )
364 0 : if ( FD_UNLIKELY( lamports_rc_vaddr >= FD_VM_MEM_MAP_INPUT_REGION_START ) ) {
365 0 : return FD_VM_SYSCALL_ERR_INVALID_POINTER;
366 0 : }
367 0 : #endif
368 :
369 : /* https://github.com/anza-xyz/agave/blob/v2.1.7/programs/bpf_loader/src/syscalls/cpi.rs#L144 */
370 0 : VM_SYSCALL_CPI_ACC_INFO_LAMPORTS_VADDR( vm, (account_infos + j), lamports_vaddr )
371 0 : ulong expected_lamports_vaddr = serialized_lamports_vaddr( vm, acc_region_meta );
372 0 : if( FD_UNLIKELY( lamports_vaddr!=expected_lamports_vaddr )) {
373 : /* Max msg_sz: 45 + 18 + 18 = 81 < 127 => we can use printf */
374 0 : fd_log_collector_printf_dangerous_max_127( vm->instr_ctx,
375 0 : "Invalid account info pointer `lamports': %#lx != %#lx", lamports_vaddr, expected_lamports_vaddr );
376 0 : return FD_VM_SYSCALL_ERR_INVALID_POINTER;
377 0 : }
378 :
379 : /* Check that the account's data Rc<RefCell<T>> is not stored in the account. Because a refcell is
380 : only present if the Rust SDK is used, we only need to check this for the Rust SDK.
381 :
382 : https://github.com/anza-xyz/agave/blob/v2.1.7/programs/bpf_loader/src/syscalls/cpi.rs#L161 */
383 : #ifdef VM_SYSCALL_CPI_ACC_INFO_DATA_RC_REFCELL_VADDR
384 0 : VM_SYSCALL_CPI_ACC_INFO_DATA_RC_REFCELL_VADDR( vm, (account_infos + j), data_rc_vaddr )
385 0 : if( FD_UNLIKELY( data_rc_vaddr >= FD_VM_MEM_MAP_INPUT_REGION_START ) ) {
386 0 : return FD_VM_SYSCALL_ERR_INVALID_POINTER;
387 0 : }
388 0 : #endif
389 :
390 : /* https://github.com/anza-xyz/agave/blob/v2.1.7/programs/bpf_loader/src/syscalls/cpi.rs#L172 */
391 0 : ulong expected_data_region_vaddr = FD_VM_MEM_MAP_INPUT_REGION_START +
392 0 : vm->input_mem_regions[acc_region_meta->region_idx].vaddr_offset;
393 0 : VM_SYSCALL_CPI_ACC_INFO_DATA_VADDR( vm, (account_infos + j), data_vaddr )
394 0 : if( FD_UNLIKELY( data_vaddr!=expected_data_region_vaddr )) {
395 : /* Max msg_sz: 41 + 18 + 18 = 77 < 127 => we can use printf */
396 0 : fd_log_collector_printf_dangerous_max_127( vm->instr_ctx,
397 0 : "Invalid account info pointer `data': %#lx != %#lx", data_vaddr, expected_data_region_vaddr );
398 0 : return FD_VM_SYSCALL_ERR_INVALID_POINTER;
399 0 : }
400 0 : }
401 :
402 : /* Update the callee account to reflect any changes the caller has made */
403 1098 : if( FD_UNLIKELY( VM_SYCALL_CPI_UPDATE_CALLEE_ACC_FUNC(vm, &account_infos[j], (uchar)index_in_caller ) ) ) {
404 0 : return 1001;
405 0 : }
406 1098 : }
407 :
408 1098 : if( !found ) {
409 : /* TODO: magic number */
410 0 : return 1002;
411 0 : }
412 1098 : }
413 :
414 252 : return FD_VM_SUCCESS;
415 252 : }
416 :
417 : /* fd_vm_cpi_update_caller_acc_{rust/c} mirrors the behaviour of
418 : solana_bpf_loader_program::syscalls::cpi::update_caller_account:
419 : https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/programs/bpf_loader/src/syscalls/cpi.rs#L1291
420 :
421 : This method should be called after a CPI instruction execution has
422 : returned. It updates the given caller account info with any changes the callee
423 : has made to this account during execution, so that those changes are
424 : reflected in the rest of the caller's execution.
425 :
426 : Those changes will be in the instructions borrowed accounts cache.
427 :
428 : Paramaters:
429 : - vm: handle to the vm
430 : - caller_acc_info: caller account info object, which should be updated
431 : - pubkey: pubkey of the account
432 :
433 : TODO: error codes
434 : */
435 768 : #define VM_SYSCALL_CPI_UPDATE_CALLER_ACC_FUNC FD_EXPAND_THEN_CONCAT2(fd_vm_cpi_update_caller_acc_, VM_SYSCALL_CPI_ABI)
436 : static int
437 : VM_SYSCALL_CPI_UPDATE_CALLER_ACC_FUNC( fd_vm_t * vm,
438 : VM_SYSCALL_CPI_ACC_INFO_T * caller_acc_info,
439 : uchar instr_acc_idx,
440 768 : fd_pubkey_t const * pubkey ) {
441 :
442 768 : if( !vm->direct_mapping ) {
443 : /* Look up the borrowed account from the instruction context, which will contain
444 : the callee's changes. */
445 768 : fd_borrowed_account_t * callee_acc_rec = NULL;
446 768 : int err = fd_instr_borrowed_account_view( vm->instr_ctx, pubkey, &callee_acc_rec );
447 768 : if( FD_UNLIKELY( err && ( err != FD_ACC_MGR_ERR_UNKNOWN_ACCOUNT ) ) ) {
448 0 : return 1;
449 0 : }
450 :
451 : /* Update the caller account lamports with the value from the callee */
452 2304 : VM_SYSCALL_CPI_ACC_INFO_LAMPORTS( vm, caller_acc_info, caller_acc_lamports );
453 2304 : *caller_acc_lamports = callee_acc_rec->const_meta->info.lamports;
454 :
455 : /* Update the caller account owner with the value from the callee */
456 2304 : uchar const * updated_owner = callee_acc_rec->const_meta->info.owner;
457 2304 : uchar * caller_acc_owner = FD_VM_MEM_HADDR_ST( vm, caller_acc_info->owner_addr, alignof(uchar), sizeof(fd_pubkey_t) );
458 768 : if( updated_owner ) fd_memcpy( caller_acc_owner, updated_owner, sizeof(fd_pubkey_t) );
459 0 : else fd_memset( caller_acc_owner, 0, sizeof(fd_pubkey_t) );
460 :
461 : /* Update the caller account data with the value from the callee */
462 1536 : VM_SYSCALL_CPI_ACC_INFO_DATA( vm, caller_acc_info, caller_acc_data );
463 :
464 1536 : ulong const updated_data_len = callee_acc_rec->const_meta->dlen;
465 1536 : if( !updated_data_len ) fd_memset( (void*)caller_acc_data, 0, caller_acc_data_len );
466 :
467 1536 : if( caller_acc_data_len != updated_data_len ) {
468 : /* FIXME: missing MAX_PERMITTED_DATA_INCREASE check from solana
469 : https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/programs/bpf_loader/src/syscalls/cpi.rs#L1342 */
470 :
471 : /* FIXME: do we need to zero the memory that was previously used, if the new data_len is smaller?
472 : https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/programs/bpf_loader/src/syscalls/cpi.rs#L1361
473 : I don't think we do but need to double-check. */
474 :
475 0 : VM_SYSCALL_CPI_SET_ACC_INFO_DATA_LEN( vm, caller_acc_info, caller_acc_data, updated_data_len );
476 :
477 : /* Update the serialized len field
478 : https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/programs/bpf_loader/src/syscalls/cpi.rs#L1437 */
479 0 : ulong * caller_len = FD_VM_MEM_HADDR_ST( vm, fd_ulong_sat_sub(caller_acc_data_vm_addr, sizeof(ulong)), alignof(ulong), sizeof(ulong) );
480 0 : *caller_len = updated_data_len;
481 :
482 : /* FIXME return instruction error account data size too small in the same scenarios solana does
483 : https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/programs/bpf_loader/src/syscalls/cpi.rs#L1534 */
484 0 : }
485 :
486 768 : fd_memcpy( (void*)caller_acc_data, callee_acc_rec->const_data, updated_data_len );
487 768 : } else { /* Direct mapping enabled */
488 :
489 : /* Look up the borrowed account from the instruction context, which will
490 : contain the callee's changes. */
491 0 : fd_borrowed_account_t * callee_acc_rec = NULL;
492 0 : int err = fd_instr_borrowed_account_view( vm->instr_ctx, pubkey, &callee_acc_rec );
493 0 : if( FD_UNLIKELY( err && err!=FD_ACC_MGR_ERR_UNKNOWN_ACCOUNT ) ) {
494 0 : return 1;
495 0 : }
496 :
497 : /* Update the caller account lamports with the value from the callee */
498 0 : VM_SYSCALL_CPI_ACC_INFO_LAMPORTS( vm, caller_acc_info, caller_acc_lamports );
499 0 : *caller_acc_lamports = callee_acc_rec->const_meta->info.lamports;
500 :
501 : /* Update the caller account owner with the value from the callee */
502 0 : uchar const * updated_owner = callee_acc_rec->const_meta->info.owner;
503 0 : uchar * caller_acc_owner = (uchar*)FD_VM_MEM_HADDR_ST( vm, caller_acc_info->owner_addr, alignof(uchar), sizeof(fd_pubkey_t) );
504 0 : if( updated_owner ) {
505 0 : fd_memcpy( caller_acc_owner, updated_owner, sizeof(fd_pubkey_t) );
506 0 : } else {
507 0 : fd_memset( caller_acc_owner, 0, sizeof(fd_pubkey_t) );
508 0 : }
509 :
510 : /* Make sure that the capacity of the borrowed account is sized up in case
511 : it was shrunk in the CPI. It needs to be sized up in order to fit within
512 : the originally delinated regions when the account data was serialized.
513 : https://github.com/anza-xyz/agave/blob/36323b6dcd3e29e4d6fe6d73d716a3f33927148b/programs/bpf_loader/src/syscalls/cpi.rs#L1311 */
514 0 : VM_SYSCALL_CPI_ACC_INFO_METADATA( vm, caller_acc_info, caller_acc_data );
515 0 : ulong region_idx = vm->acc_region_metas[ instr_acc_idx ].region_idx;
516 0 : uint original_len = vm->acc_region_metas[ instr_acc_idx ].has_data_region ?
517 0 : vm->input_mem_regions[ region_idx ].region_sz : 0U;
518 :
519 0 : uchar zero_all_mapped_spare_capacity = 0;
520 : /* This case can only be triggered if the original length is more than 0 */
521 0 : if( callee_acc_rec->const_meta->dlen < original_len ) {
522 0 : ulong new_len = callee_acc_rec->const_meta->dlen;
523 : /* Allocate into the buffer to make sure that the original data len
524 : is still valid but don't change the dlen. Zero out the rest of the
525 : memory which is not used. */
526 0 : err = fd_instr_borrowed_account_modify( vm->instr_ctx, pubkey, original_len, &callee_acc_rec );
527 0 : if( FD_UNLIKELY( err!=FD_ACC_MGR_SUCCESS ) ) {
528 0 : return 1;
529 0 : }
530 0 : callee_acc_rec->meta->dlen = new_len;
531 0 : zero_all_mapped_spare_capacity = 1;
532 0 : }
533 :
534 : /* Update the account data region if an account data region exists. We
535 : know that one exists iff the original len was non-zero. */
536 0 : ulong acc_region_idx = vm->acc_region_metas[instr_acc_idx].region_idx;
537 0 : if( original_len && vm->input_mem_regions[ acc_region_idx ].haddr!=(ulong)callee_acc_rec->data ) {
538 0 : vm->input_mem_regions[ acc_region_idx ].haddr = (ulong)callee_acc_rec->data;
539 0 : zero_all_mapped_spare_capacity = 1;
540 0 : }
541 :
542 0 : ulong prev_len = caller_acc_data_len;
543 0 : ulong post_len = callee_acc_rec->const_meta->dlen;
544 :
545 : /* Do additional handling in the case where the data size has changed in
546 : the course of the callee's CPI. */
547 0 : if( prev_len!=post_len ) {
548 : /* There is an illegal data overflow if the post len is greater than the
549 : original data len + the max resizing limit (10KiB). Can't resize the
550 : account if the deprecated loader is being used */
551 0 : ulong max_increase = vm->is_deprecated ? 0UL : 10240UL;
552 0 : if( FD_UNLIKELY( post_len>fd_ulong_sat_add( (ulong)original_len, max_increase ) ) ) {
553 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_REALLOC;
554 0 : }
555 : /* There is additonal handling in the case where the account is larger
556 : than it was previously, but it has still grown since it was initially
557 : serialized. To handle this, we need to just zero out the now unused
558 : space in the account resizing region. */
559 0 : if( post_len<prev_len && prev_len>original_len ) {
560 0 : ulong dirty_realloc_start = fd_ulong_max( post_len, original_len );
561 0 : ulong dirty_realloc_len = fd_ulong_sat_sub( prev_len, dirty_realloc_start );
562 : /* We don't have to worry about multiple region writes here since we
563 : know the amount to zero out is located in the account's data
564 : resizing region. We intentionally write to the pointer despite
565 : loading it in because we assume that the permissions were changed
566 : in the callee. */
567 0 : uchar * dirty_region = FD_VM_MEM_HADDR_ST_WRITE_UNCHECKED( vm, caller_acc_data_vm_addr + dirty_realloc_start,
568 0 : alignof(uchar), dirty_realloc_len );
569 0 : fd_memset( dirty_region, 0, dirty_realloc_len );
570 0 : }
571 :
572 : /* Because the account data length changed from before to after the
573 : CPI we must update the fields appropriately. */
574 0 : VM_SYSCALL_CPI_SET_ACC_INFO_DATA_LEN( vm, caller_acc_info, caller_acc_data, post_len );
575 0 : ulong * caller_len = FD_VM_MEM_HADDR_ST( vm, fd_ulong_sat_sub( caller_acc_data_vm_addr, sizeof(ulong) ), alignof(ulong), sizeof(ulong) );
576 0 : *caller_len = post_len;
577 0 : }
578 :
579 : /* We need to zero out the end of the account data buffer if the account
580 : shrunk in size. This is because the bytes are accessible from within
581 : the VM but should be equal to zero to prevent undefined behavior. If
582 : prev_len > post_len, then dlen should be equal to original_len. */
583 0 : ulong spare_len = fd_ulong_sat_sub( fd_ulong_if( zero_all_mapped_spare_capacity, original_len, prev_len ), post_len );
584 0 : if( FD_UNLIKELY( spare_len ) ) {
585 0 : if( callee_acc_rec->const_meta->dlen>spare_len ) {
586 0 : memset( callee_acc_rec->data+callee_acc_rec->const_meta->dlen-spare_len, 0, spare_len );
587 0 : }
588 0 : }
589 :
590 0 : ulong realloc_bytes_used = fd_ulong_sat_sub( post_len, original_len );
591 0 : if( realloc_bytes_used && !vm->is_deprecated ) {
592 : /* We intentionally do a load in the case where we are writing to because
593 : we want to ignore the write checks. We load from the first byte of the
594 : resizing region */
595 0 : ulong resizing_idx = vm->acc_region_metas[ instr_acc_idx ].region_idx;
596 0 : if( vm->acc_region_metas[ instr_acc_idx ].has_data_region ) {
597 0 : resizing_idx++;
598 0 : }
599 0 : uchar * to_slice = (uchar*)vm->input_mem_regions[ resizing_idx ].haddr;
600 0 : uchar * from_slice = callee_acc_rec->data + original_len;
601 :
602 0 : fd_memcpy( to_slice, from_slice, realloc_bytes_used );
603 0 : }
604 0 : }
605 :
606 768 : return FD_VM_SUCCESS;
607 768 : }
608 :
609 : /* fd_vm_syscall_cpi_{rust/c} is the entrypoint for the sol_invoke_signed_{rust/c} syscalls.
610 :
611 : The bulk of the high-level logic mirrors Solana's cpi_common entrypoint function at
612 : https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/programs/bpf_loader/src/syscalls/cpi.rs#L1060
613 : The only differences should be in the order of the error checks, which does not affect consensus.
614 :
615 : 100-foot flow:
616 : - Translate the CPI ABI structures to the FD runtime's instruction format
617 : - Update the callee accounts with any changes made by the caller prior to this CPI instruction
618 : - Dispatch the instruction to the FD runtime (actually making the CPI call)
619 : - Update the caller accounts with any changes made by the callee during CPI execution
620 :
621 : Paramaters:
622 : - vm: pointer to the virtual machine handle
623 : - instruction_va: vm address of the instruction to execute, which will be in the language-specific ABI format.
624 : - acct_infos_va: vm address of the account infos, which will be in the language-specific ABI format.
625 : - acct_info_cnt: number of account infos
626 : - signers_seeds_va: vm address of the signers seeds
627 : - signers_seeds_cnt: number of signers seeds
628 : - _ret: pointer to the return value
629 : */
630 : #define VM_SYSCALL_CPI_ENTRYPOINT FD_EXPAND_THEN_CONCAT2(fd_vm_syscall_cpi_, VM_SYSCALL_CPI_ABI)
631 : int
632 : VM_SYSCALL_CPI_ENTRYPOINT( void * _vm,
633 : ulong instruction_va,
634 : ulong acct_infos_va,
635 : ulong acct_info_cnt,
636 : ulong signers_seeds_va,
637 : ulong signers_seeds_cnt,
638 258 : ulong * _ret ) {
639 :
640 258 : fd_vm_t * vm = (fd_vm_t *)_vm;
641 :
642 258 : FD_VM_CU_UPDATE( vm, FD_VM_INVOKE_UNITS );
643 :
644 : /* Translate instruction ********************************************/
645 : /* translate_instruction is the first thing that agave does
646 : https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/programs/bpf_loader/src/syscalls/cpi.rs#L1089 */
647 :
648 : /* Translating the CPI instruction
649 : https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/programs/bpf_loader/src/syscalls/cpi.rs#L420-L424 */
650 255 : VM_SYSCALL_CPI_INSTR_T const * cpi_instruction =
651 765 : FD_VM_MEM_HADDR_LD( vm, instruction_va, VM_SYSCALL_CPI_INSTR_ALIGN, VM_SYSCALL_CPI_INSTR_SIZE );
652 :
653 : /* Translate the program ID */
654 255 : fd_pubkey_t const * program_id = (fd_pubkey_t *)VM_SYSCALL_CPI_INSTR_PROGRAM_ID( vm, cpi_instruction );
655 :
656 : /* Translate CPI account metas *************************************************/
657 255 : VM_SYSCALL_CPI_ACC_META_T const * cpi_account_metas =
658 510 : FD_VM_MEM_SLICE_HADDR_LD( vm, VM_SYSCALL_CPI_INSTR_ACCS_ADDR( cpi_instruction ),
659 510 : VM_SYSCALL_CPI_ACC_META_ALIGN,
660 510 : fd_ulong_sat_mul( VM_SYSCALL_CPI_INSTR_ACCS_LEN( cpi_instruction ), VM_SYSCALL_CPI_ACC_META_SIZE ) );
661 :
662 : /* Agave consumes CU in translate_instruction
663 : https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/programs/bpf_loader/src/syscalls/cpi.rs#L445 */
664 255 : if( FD_FEATURE_ACTIVE( vm->instr_ctx->slot_ctx, loosen_cpi_size_restriction ) ) {
665 0 : FD_VM_CU_UPDATE( vm, VM_SYSCALL_CPI_INSTR_DATA_LEN( cpi_instruction ) / FD_VM_CPI_BYTES_PER_UNIT );
666 0 : }
667 :
668 : /* Derive PDA signers ************************************************/
669 255 : fd_pubkey_t signers[ FD_CPI_MAX_SIGNER_CNT ] = {0};
670 255 : fd_pubkey_t * caller_program_id = &vm->instr_ctx->txn_ctx->accounts[ vm->instr_ctx->instr->program_id ];
671 : /* This is the equivalent of translate_slice in translate_signers:
672 : https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/programs/bpf_loader/src/syscalls/cpi.rs#L595 */
673 510 : fd_vm_vec_t const * signers_seeds = FD_VM_MEM_SLICE_HADDR_LD( vm, signers_seeds_va, FD_VM_VEC_ALIGN, fd_ulong_sat_mul( signers_seeds_cnt, FD_VM_VEC_SIZE ) );
674 : /* Right after translating, Agave checks against MAX_SIGNERS:
675 : https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/programs/bpf_loader/src/syscalls/cpi.rs#L602 */
676 255 : if( FD_UNLIKELY( signers_seeds_cnt > FD_CPI_MAX_SIGNER_CNT ) ) {
677 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_TOO_MANY_SIGNERS );
678 0 : return FD_VM_SYSCALL_ERR_TOO_MANY_SIGNERS;
679 0 : }
680 :
681 378 : for( ulong i=0UL; i<signers_seeds_cnt; i++ ) {
682 :
683 : /* This function will precompute the memory translation required and do
684 : some preflight checks. */
685 123 : void const * signer_seed_haddrs[ FD_VM_PDA_SEEDS_MAX ];
686 123 : ulong signer_seed_lens [ FD_VM_PDA_SEEDS_MAX ];
687 :
688 123 : int err = fd_vm_translate_and_check_program_address_inputs( vm,
689 123 : signers_seeds[i].addr,
690 123 : signers_seeds[i].len,
691 123 : 0UL,
692 123 : signer_seed_haddrs,
693 123 : signer_seed_lens ,
694 123 : NULL );
695 123 : if( FD_UNLIKELY( err ) ) {
696 0 : return err;
697 0 : }
698 :
699 123 : err = fd_vm_derive_pda( vm, caller_program_id, signer_seed_haddrs, signer_seed_lens, signers_seeds[i].len, NULL, &signers[i] );
700 123 : if( FD_UNLIKELY( err ) ) {
701 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_BAD_SEEDS );
702 0 : return FD_VM_SYSCALL_ERR_BAD_SEEDS;
703 0 : }
704 123 : }
705 :
706 : /* Translate instruction data *************************************************/
707 :
708 510 : uchar const * data = FD_VM_MEM_SLICE_HADDR_LD(
709 510 : vm, VM_SYSCALL_CPI_INSTR_DATA_ADDR( cpi_instruction ),
710 510 : FD_VM_ALIGN_RUST_U8,
711 510 : VM_SYSCALL_CPI_INSTR_DATA_LEN( cpi_instruction ));
712 :
713 : /* Authorized program check *************************************************/
714 :
715 255 : if( FD_UNLIKELY( fd_vm_syscall_cpi_check_authorized_program( program_id, vm->instr_ctx->slot_ctx, data, VM_SYSCALL_CPI_INSTR_DATA_LEN( cpi_instruction ) ) ) ) {
716 : /* https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/programs/bpf_loader/src/syscalls/cpi.rs#L1054 */
717 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_PROGRAM_NOT_SUPPORTED );
718 0 : return FD_VM_SYSCALL_ERR_PROGRAM_NOT_SUPPORTED;
719 0 : }
720 :
721 : /* Instruction checks ***********************************************/
722 :
723 255 : int err = fd_vm_syscall_cpi_check_instruction( vm, VM_SYSCALL_CPI_INSTR_ACCS_LEN( cpi_instruction ), VM_SYSCALL_CPI_INSTR_DATA_LEN( cpi_instruction ) );
724 255 : if( FD_UNLIKELY( err ) ) {
725 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, err );
726 0 : return err;
727 0 : }
728 :
729 : /* Create the instruction to execute (in the input format the FD runtime expects) from
730 : the translated CPI ABI inputs. */
731 255 : fd_instr_info_t * instruction_to_execute = &vm->instr_ctx->txn_ctx->instr_infos[ vm->instr_ctx->txn_ctx->instr_info_cnt ];
732 :
733 255 : vm->instr_ctx->txn_ctx->instr_info_cnt++;
734 255 : if( FD_UNLIKELY( vm->instr_ctx->txn_ctx->instr_info_cnt>FD_MAX_INSTRUCTION_TRACE_LENGTH ) ) {
735 0 : return FD_EXECUTOR_INSTR_ERR_MAX_INSN_TRACE_LENS_EXCEEDED;;
736 0 : }
737 :
738 255 : err = VM_SYSCALL_CPI_INSTRUCTION_TO_INSTR_FUNC( vm, cpi_instruction, cpi_account_metas, program_id, data, instruction_to_execute );
739 255 : if( FD_UNLIKELY( err ) ) return err;
740 :
741 : /* Prepare the instruction for execution in the runtime. This is required by the runtime
742 : before we can pass an instruction to the executor. */
743 255 : fd_instruction_account_t instruction_accounts[256];
744 255 : ulong instruction_accounts_cnt;
745 255 : err = fd_vm_prepare_instruction( vm->instr_ctx->instr, instruction_to_execute, vm->instr_ctx, instruction_accounts, &instruction_accounts_cnt, signers, signers_seeds_cnt );
746 255 : if( FD_UNLIKELY( err ) ) {
747 : /* We should propogate the instruction error from fd_vm_prepare_instruction. */
748 3 : FD_VM_ERR_FOR_LOG_INSTR( vm, err );
749 3 : return err;
750 3 : }
751 :
752 : /* Translate account infos ******************************************/
753 : /* Direct mapping check
754 : https://github.com/anza-xyz/agave/blob/v2.1.0/programs/bpf_loader/src/syscalls/cpi.rs#L805-L814 */
755 252 : ulong acc_info_total_sz = fd_ulong_sat_mul( acct_info_cnt, VM_SYSCALL_CPI_ACC_INFO_SIZE );
756 252 : if( FD_UNLIKELY( vm->direct_mapping && fd_ulong_sat_add( acct_infos_va, acc_info_total_sz ) >= FD_VM_MEM_MAP_INPUT_REGION_START ) ) {
757 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_INVALID_POINTER );
758 0 : return FD_VM_SYSCALL_ERR_INVALID_POINTER;
759 0 : }
760 :
761 : /* This is the equivalent of translate_slice in translate_account_infos:
762 : https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/programs/bpf_loader/src/syscalls/cpi.rs#L816 */
763 504 : VM_SYSCALL_CPI_ACC_INFO_T * acc_infos = FD_VM_MEM_SLICE_HADDR_ST( vm, acct_infos_va, VM_SYSCALL_CPI_ACC_INFO_ALIGN, acc_info_total_sz );
764 :
765 : /* Right after translating, Agave checks the number of account infos:
766 : https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/programs/bpf_loader/src/syscalls/cpi.rs#L822 */
767 252 : if( FD_FEATURE_ACTIVE( vm->instr_ctx->slot_ctx, loosen_cpi_size_restriction ) ) {
768 0 : if( FD_UNLIKELY( acct_info_cnt > get_cpi_max_account_infos( vm->instr_ctx->slot_ctx ) ) ) {
769 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_MAX_INSTRUCTION_ACCOUNT_INFOS_EXCEEDED );
770 0 : return FD_VM_SYSCALL_ERR_MAX_INSTRUCTION_ACCOUNT_INFOS_EXCEEDED;
771 0 : }
772 252 : } else {
773 252 : ulong adjusted_len = fd_ulong_sat_mul( acct_info_cnt, sizeof( fd_pubkey_t ) );
774 252 : if ( FD_UNLIKELY( adjusted_len > FD_VM_MAX_CPI_INSTRUCTION_SIZE ) ) {
775 : /* "Cap the number of account_infos a caller can pass to approximate
776 : maximum that accounts that could be passed in an instruction" */
777 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_TOO_MANY_ACCOUNTS );
778 0 : return FD_VM_SYSCALL_ERR_TOO_MANY_ACCOUNTS;
779 0 : }
780 252 : }
781 :
782 : /* Update the callee accounts with any changes made by the caller prior to this CPI execution */
783 252 : ulong callee_account_keys[256];
784 252 : ulong caller_accounts_to_update[256];
785 252 : ulong caller_accounts_to_update_len = 0;
786 252 : err = VM_SYSCALL_CPI_TRANSLATE_AND_UPDATE_ACCOUNTS_FUNC( vm, instruction_accounts, instruction_accounts_cnt, acc_infos, acct_info_cnt, callee_account_keys, caller_accounts_to_update, &caller_accounts_to_update_len );
787 252 : if( FD_UNLIKELY( err ) ) return err;
788 :
789 : /* Set the transaction compute meter to be the same as the VM's compute meter,
790 : so that the callee cannot use compute units that the caller has already used. */
791 252 : vm->instr_ctx->txn_ctx->compute_meter = vm->cu;
792 :
793 : /* Execute the CPI instruction in the runtime */
794 252 : int err_exec = fd_execute_instr( vm->instr_ctx->txn_ctx, instruction_to_execute );
795 252 : ulong instr_exec_res = (ulong)err_exec;
796 :
797 : /* Set the CU meter to the instruction context's transaction context's compute meter,
798 : so that the caller can't use compute units that the callee has already used. */
799 252 : vm->cu = vm->instr_ctx->txn_ctx->compute_meter;
800 :
801 252 : *_ret = instr_exec_res;
802 :
803 252 : if( FD_UNLIKELY( err_exec ) ) return err_exec;
804 :
805 : /* https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/programs/bpf_loader/src/syscalls/cpi.rs#L1128-L1145 */
806 : /* Update all account permissions before updating the account data updates.
807 : We have inlined the anza function update_caller_account_perms here.
808 : TODO: consider factoring this out */
809 240 : if( vm->direct_mapping ) {
810 0 : for( ulong i=0UL; i<vm->instr_ctx->instr->acct_cnt; i++ ) {
811 : /* https://github.com/firedancer-io/solana/blob/508f325e19c0fd8e16683ea047d7c1a85f127e74/programs/bpf_loader/src/syscalls/cpi.rs#L939-L943 */
812 : /* Anza only even attemps to update the account permissions if it is a
813 : "caller account". Only writable accounts are caller accounts. */
814 0 : if( fd_instr_acc_is_writable_idx( vm->instr_ctx->instr, i ) ) {
815 :
816 0 : uint is_writable = (uint)fd_account_can_data_be_changed( vm->instr_ctx, i, &err );
817 : /* Lookup memory regions for the account data and the realloc region. */
818 0 : ulong data_region_idx = vm->acc_region_metas[i].has_data_region ? vm->acc_region_metas[i].region_idx : 0;
819 0 : ulong realloc_region_idx = vm->acc_region_metas[i].has_resizing_region ? vm->acc_region_metas[i].region_idx : 0;
820 0 : if( data_region_idx && realloc_region_idx ) {
821 0 : realloc_region_idx++;
822 0 : }
823 :
824 0 : if( data_region_idx ) {
825 0 : vm->input_mem_regions[ data_region_idx ].is_writable = is_writable;
826 0 : }
827 0 : if( FD_LIKELY( realloc_region_idx ) ) { /* Unless is deprecated loader */
828 0 : vm->input_mem_regions[ realloc_region_idx ].is_writable = is_writable;
829 0 : }
830 0 : }
831 0 : }
832 0 : }
833 :
834 : /* Update the caller accounts with any changes made by the callee during CPI execution */
835 1008 : for( ulong i=0UL; i<caller_accounts_to_update_len; i++ ) {
836 : /* https://github.com/firedancer-io/solana/blob/508f325e19c0fd8e16683ea047d7c1a85f127e74/programs/bpf_loader/src/syscalls/cpi.rs#L939-L943 */
837 : /* We only want to update the writable accounts, because the non-writable
838 : caller accounts can't be changed during a CPI execution. */
839 768 : if( fd_instr_acc_is_writable_idx( vm->instr_ctx->instr, callee_account_keys[i] ) ) {
840 768 : fd_pubkey_t const * callee = &vm->instr_ctx->instr->acct_pubkeys[callee_account_keys[i]];
841 768 : err = VM_SYSCALL_CPI_UPDATE_CALLER_ACC_FUNC(vm, &acc_infos[caller_accounts_to_update[i]], (uchar)callee_account_keys[i], callee);
842 768 : if( FD_UNLIKELY( err ) ) return err;
843 768 : }
844 768 : }
845 :
846 240 : return FD_VM_SUCCESS;
847 240 : }
848 :
849 : #undef VM_SYSCALL_CPI_UPDATE_CALLER_ACC_FUNC
850 : #undef VM_SYSCALL_CPI_FROM_ACC_INFO_FUNC
851 : #undef VM_SYSCALL_CPI_TRANSLATE_AND_UPDATE_ACCOUNTS_FUNC
852 : #undef VM_SYSCALL_CPI_INSTRUCTION_TO_INSTR_FUNC
853 : #undef VM_SYSCALL_CPI_FUNC
|