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 : /* https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L23
18 :
19 : This is used for checking that the account info pointers given by the
20 : user match up with the addresses in the serialized account metadata.
21 :
22 : Field name length is restricted to 54 because
23 : 127 - (37 + 18 + 18) leaves 54 characters for the field name
24 : */
25 : #define VM_SYSCALL_CPI_CHECK_ACCOUNT_INFO_POINTER_FIELD_MAX_54(vm, vm_addr, expected_vm_addr, field_name) \
26 0 : if( FD_UNLIKELY( vm_addr!=expected_vm_addr )) { \
27 0 : fd_log_collector_printf_dangerous_max_127( vm->instr_ctx, \
28 0 : "Invalid account info pointer `%s': %#lx != %#lx", field_name, vm_addr, expected_vm_addr ); \
29 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_INVALID_POINTER ); \
30 0 : return FD_VM_SYSCALL_ERR_INVALID_POINTER; \
31 0 : }
32 :
33 : /* fd_vm_syscall_cpi_instruction_to_instr_{c/rust} takes the translated
34 : CPI ABI structures (instruction and account meta list), and uses these
35 : to populate a fd_instr_info_t struct. This struct can then be given to the
36 : FD runtime for execution.
37 :
38 : WARNING: out_instr will be partially filled if there are unmatched account
39 : metas (i.e,. no corresponding entry in the transaction accounts list). This
40 : is not an error condition. fd_vm_prepare_instruction has to handle that case
41 : in order to match Agave's behavior of checking presence in both transaction
42 : accounts list and caller instruction accounts list in a single loop iteration.
43 :
44 : Parameters:
45 : - vm: handle to the vm
46 : - cpi_instr: instruction to execute laid out in the CPI ABI format (Rust or C)
47 : - cpi_acc_metas: list of account metas, again in the CPI ABI format
48 : - signers: derived signers for this CPI call
49 : - signers_cnt: length of the signers list
50 : - cpi_instr_data: instruction data in host address space
51 :
52 : TODO: return codes/errors?
53 : */
54 0 : #define VM_SYSCALL_CPI_INSTRUCTION_TO_INSTR_FUNC FD_EXPAND_THEN_CONCAT2(fd_vm_syscall_cpi_instruction_to_instr_, VM_SYSCALL_CPI_ABI)
55 : static int
56 : VM_SYSCALL_CPI_INSTRUCTION_TO_INSTR_FUNC( fd_vm_t * vm,
57 : VM_SYSCALL_CPI_INSTR_T const * cpi_instr,
58 : VM_SYSCALL_CPI_ACC_META_T const * cpi_acct_metas,
59 : fd_pubkey_t const * program_id,
60 : uchar const * cpi_instr_data,
61 : fd_instr_info_t * out_instr,
62 0 : fd_pubkey_t out_instr_acct_keys[ FD_INSTR_ACCT_MAX ] ) {
63 0 : out_instr->program_id = UCHAR_MAX;
64 0 : out_instr->data_sz = (ushort)VM_SYSCALL_CPI_INSTR_DATA_LEN( cpi_instr );
65 0 : out_instr->data = (uchar *)cpi_instr_data;
66 0 : out_instr->acct_cnt = (ushort)VM_SYSCALL_CPI_INSTR_ACCS_LEN( cpi_instr );
67 :
68 : /* Find the index of the CPI instruction's program account in the transaction */
69 0 : int program_id_idx = fd_exec_txn_ctx_find_index_of_account( vm->instr_ctx->txn_ctx, program_id );
70 0 : if( FD_LIKELY( program_id_idx != -1 ) ) {
71 0 : out_instr->program_id = (uchar)program_id_idx;
72 0 : }
73 :
74 0 : uchar acc_idx_seen[ FD_INSTR_ACCT_MAX ] = {0};
75 :
76 0 : for( ushort i=0; i<VM_SYSCALL_CPI_INSTR_ACCS_LEN( cpi_instr ); i++ ) {
77 0 : VM_SYSCALL_CPI_ACC_META_T const * cpi_acct_meta = &cpi_acct_metas[i];
78 0 : fd_pubkey_t const * pubkey = fd_type_pun_const( VM_SYSCALL_CPI_ACC_META_PUBKEY( vm, cpi_acct_meta ) );
79 0 : out_instr_acct_keys[i] = *pubkey;
80 :
81 : /* The parent flag(s) for is writable/signer is checked in
82 : fd_vm_prepare_instruction. Signer privilege is allowed iff the account
83 : is a signer in the caller or if it is a derived signer. */
84 : /* TODO: error if flags are wrong */
85 0 : out_instr->accounts[i] = fd_instruction_account_init( USHORT_MAX,
86 0 : USHORT_MAX,
87 0 : USHORT_MAX,
88 0 : VM_SYSCALL_CPI_ACC_META_IS_WRITABLE( cpi_acct_meta ),
89 0 : VM_SYSCALL_CPI_ACC_META_IS_SIGNER( cpi_acct_meta ) );
90 :
91 : /* Use USHORT_MAX to indicate account not found
92 : https://github.com/anza-xyz/agave/blob/v2.1.14/program-runtime/src/invoke_context.rs#L366-L370 */
93 0 : int idx_in_txn = fd_exec_txn_ctx_find_index_of_account( vm->instr_ctx->txn_ctx, pubkey );
94 0 : int idx_in_caller = fd_exec_instr_ctx_find_idx_of_instr_account( vm->instr_ctx, pubkey );
95 :
96 0 : fd_instr_info_setup_instr_account( out_instr,
97 0 : acc_idx_seen,
98 0 : idx_in_txn!=-1 ? (ushort)idx_in_txn : USHORT_MAX,
99 0 : idx_in_caller!=-1 ? (ushort)idx_in_caller : USHORT_MAX,
100 0 : i,
101 0 : VM_SYSCALL_CPI_ACC_META_IS_WRITABLE( cpi_acct_meta ),
102 0 : VM_SYSCALL_CPI_ACC_META_IS_SIGNER( cpi_acct_meta ) );
103 :
104 0 : }
105 :
106 0 : return FD_VM_SUCCESS;
107 0 : }
108 :
109 : /*
110 : fd_vm_syscall_cpi_update_callee_acc_{rust/c} corresponds to solana_bpf_loader_program::syscalls::cpi::update_callee_account:
111 : https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/programs/bpf_loader/src/syscalls/cpi.rs#L1302
112 :
113 : (the copy of the account stored in the instruction context's
114 : borrowed accounts cache)
115 :
116 : This function should be called before the CPI instruction is executed. Its purpose is to
117 : update the callee account's view of the given account with any changes the caller may made
118 : to the account before the CPI instruction is executed.
119 :
120 : The callee's view of the account is the borrowed accounts cache, so to update the
121 : callee account we look up the account in the borrowed accounts cache and update it.
122 :
123 : Paramaters:
124 : - vm: pointer to the virtual machine handle
125 : - account_info: account info object
126 : - callee_acc_pubkey: pubkey of the account. this is used to look up the account in the borrowed accounts cache
127 : (TODO: this seems redundant? we can probably remove this, as the account_info contains the pubkey)
128 : */
129 0 : #define VM_SYCALL_CPI_UPDATE_CALLEE_ACC_FUNC FD_EXPAND_THEN_CONCAT2(fd_vm_syscall_cpi_update_callee_acc_, VM_SYSCALL_CPI_ABI)
130 : static int
131 : VM_SYCALL_CPI_UPDATE_CALLEE_ACC_FUNC( fd_vm_t * vm,
132 : fd_vm_cpi_caller_account_t const * caller_account,
133 0 : uchar instr_acc_idx ) {
134 0 : int err;
135 :
136 : /* Borrow the callee account.
137 : TODO: Agave borrows before this function call. Consider refactoring to borrow the account at the same place as Agave.
138 : https://github.com/anza-xyz/agave/blob/v2.1.14/programs/bpf_loader/src/syscalls/cpi.rs#L893 */
139 0 : fd_guarded_borrowed_account_t callee_acc;
140 0 : err = fd_exec_instr_ctx_try_borrow_instr_account( vm->instr_ctx, instr_acc_idx, &callee_acc );
141 0 : if( FD_UNLIKELY( err ) ) {
142 : /* No need to do anything if the account is missing from the borrowed accounts cache */
143 0 : return FD_VM_SUCCESS;
144 0 : }
145 :
146 0 : if( fd_borrowed_account_get_lamports( &callee_acc )!=*(caller_account->lamports) ) {
147 0 : err = fd_borrowed_account_set_lamports( &callee_acc, *(caller_account->lamports) );
148 0 : if( FD_UNLIKELY( err ) ) {
149 0 : FD_VM_ERR_FOR_LOG_INSTR( vm, err );
150 0 : return -1;
151 0 : }
152 0 : }
153 :
154 0 : if( !vm->direct_mapping ) {
155 : /* Get the account data */
156 : /* Update the account data, if the account data can be changed */
157 : /* FIXME: double-check these permissions, especially the callee_acc_idx */
158 :
159 0 : if( fd_borrowed_account_can_data_be_resized( &callee_acc, caller_account->serialized_data_len, &err ) &&
160 0 : fd_borrowed_account_can_data_be_changed( &callee_acc, &err ) ) {
161 : /* We must ignore the errors here, as they are informational and do not mean the result is invalid. */
162 : /* TODO: not pass informational errors like this? */
163 :
164 0 : err = fd_borrowed_account_set_data_from_slice( &callee_acc, caller_account->serialized_data, caller_account->serialized_data_len );
165 0 : if( FD_UNLIKELY( err ) ) {
166 0 : FD_VM_ERR_FOR_LOG_INSTR( vm, err );
167 0 : return -1;
168 0 : }
169 0 : } else if( FD_UNLIKELY( caller_account->serialized_data_len!=fd_borrowed_account_get_data_len( &callee_acc ) ||
170 0 : memcmp( fd_borrowed_account_get_data( &callee_acc ), caller_account->serialized_data, caller_account->serialized_data_len ) ) ) {
171 0 : FD_VM_ERR_FOR_LOG_INSTR( vm, err );
172 0 : return -1;
173 0 : }
174 :
175 0 : } else { /* Direct mapping enabled */
176 0 : ulong * ref_to_len = FD_VM_MEM_HADDR_ST( vm, caller_account->ref_to_len_in_vm.vaddr, alignof(ulong), sizeof(ulong) );
177 0 : ulong orig_len = caller_account->orig_data_len;
178 0 : ulong prev_len = fd_borrowed_account_get_data_len( &callee_acc );
179 0 : ulong post_len = *ref_to_len;
180 :
181 0 : int err;
182 0 : if( fd_borrowed_account_can_data_be_resized( &callee_acc, post_len, &err ) &&
183 0 : fd_borrowed_account_can_data_be_changed( &callee_acc, &err ) ) {
184 :
185 0 : ulong realloc_bytes_used = fd_ulong_sat_sub( post_len, orig_len );
186 :
187 0 : if( FD_UNLIKELY( vm->is_deprecated && realloc_bytes_used ) ) {
188 0 : FD_VM_ERR_FOR_LOG_INSTR( vm, FD_EXECUTOR_INSTR_ERR_INVALID_REALLOC );
189 0 : return -1;
190 0 : }
191 :
192 0 : err = fd_borrowed_account_set_data_length( &callee_acc, post_len );
193 0 : if( FD_UNLIKELY( err ) ) {
194 0 : FD_VM_ERR_FOR_LOG_INSTR( vm, err );
195 0 : return -1;
196 0 : }
197 :
198 :
199 0 : if( realloc_bytes_used ) {
200 : /* We need to get the relevant data slice. However, we know that the
201 : current length currently exceeds the original length for the account
202 : data. This means that all of the additional bytes must exist in the
203 : account data resizing region. As an invariant, original_len must be
204 : equal to the length of the account data region. This means we can
205 : smartly look up the right region and don't need to worry about
206 : multiple region access.We just need to load in the bytes from
207 : (original len, post_len]. */
208 0 : uchar const * realloc_data = FD_VM_MEM_SLICE_HADDR_LD( vm, caller_account->vm_data_vaddr+orig_len, alignof(uchar), realloc_bytes_used );
209 :
210 0 : uchar * data = NULL;
211 0 : ulong dlen = 0UL;
212 0 : err = fd_borrowed_account_get_data_mut( &callee_acc, &data, &dlen );
213 0 : if( FD_UNLIKELY( err ) ) {
214 0 : FD_VM_ERR_FOR_LOG_INSTR( vm, err );
215 0 : return -1;
216 0 : }
217 0 : fd_memcpy( data+orig_len, realloc_data, realloc_bytes_used );
218 0 : }
219 :
220 0 : } else if( FD_UNLIKELY( prev_len!=post_len ) ) {
221 0 : FD_VM_ERR_FOR_LOG_INSTR( vm, err );
222 0 : return -1;
223 0 : }
224 0 : }
225 :
226 0 : if( FD_UNLIKELY( memcmp( fd_borrowed_account_get_owner( &callee_acc ), caller_account->owner, sizeof(fd_pubkey_t) ) ) ) {
227 0 : err = fd_borrowed_account_set_owner( &callee_acc, caller_account->owner );
228 0 : if( FD_UNLIKELY( err ) ) {
229 0 : FD_VM_ERR_FOR_LOG_INSTR( vm, err );
230 0 : return -1;
231 0 : }
232 0 : }
233 :
234 0 : return FD_VM_SUCCESS;
235 0 : }
236 :
237 : /*
238 : fd_vm_syscall_cpi_translate_and_update_accounts_ mirrors the behaviour of
239 : solana_bpf_loader_program::syscalls::cpi::translate_and_update_accounts:
240 : https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/programs/bpf_loader/src/syscalls/cpi.rs#L954-L1085
241 :
242 : It translates the caller accounts to the host address space, and then calls
243 : fd_vm_syscall_cpi_update_callee_acc to update the callee borrowed account with any changes
244 : the caller has made to the account during execution before this CPI call.
245 :
246 : It also populates the out_callee_indices and out_caller_indices arrays:
247 : - out_callee_indices: indices of the callee accounts in the transaction
248 : - out_caller_indices: indices of the caller accounts in the account_infos array
249 :
250 : Parameters:
251 : - vm: pointer to the virtual machine handle
252 : - instruction_accounts: array of instruction accounts
253 : - instruction_accounts_cnt: length of the instruction_accounts array
254 : - account_infos: array of account infos
255 : - account_infos_length: length of the account_infos array
256 :
257 : Populates the given out_callee_indices and out_caller_indices arrays:
258 : - out_callee_indices: indices of the callee accounts in the transaction
259 : - out_caller_indices: indices of the caller accounts in the account_infos array
260 : - out_len: length of the out_callee_indices and out_caller_indices arrays
261 : */
262 0 : #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)
263 : static int
264 : VM_SYSCALL_CPI_TRANSLATE_AND_UPDATE_ACCOUNTS_FUNC(
265 : fd_vm_t * vm,
266 : fd_instruction_account_t const * instruction_accounts,
267 : ulong const instruction_accounts_cnt,
268 : ulong acct_infos_va,
269 : fd_pubkey_t const * * account_info_keys, /* same length as account_infos_length */
270 : VM_SYSCALL_CPI_ACC_INFO_T const * account_infos,
271 : ulong const account_infos_length,
272 : ushort * out_callee_indices,
273 : ushort * out_caller_indices,
274 : fd_vm_cpi_caller_account_t * caller_accounts,
275 0 : ulong * out_len ) {
276 0 : for( ulong i=0UL; i<instruction_accounts_cnt; i++ ) {
277 0 : if( i!=instruction_accounts[i].index_in_callee ) {
278 : /* Skip duplicate accounts */
279 0 : continue;
280 0 : }
281 :
282 : /* `fd_vm_prepare_instruction()` will always set up a valid index for `index_in_caller`, so we can access the borrowed account directly.
283 : A borrowed account will always have non-NULL meta (if the account doesn't exist, `fd_executor_setup_accounts_for_txn()`
284 : will set its meta up) */
285 :
286 : /* https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/programs/bpf_loader/src/syscalls/cpi.rs#L878-L881 */
287 0 : fd_guarded_borrowed_account_t callee_acct;
288 0 : FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( vm->instr_ctx, instruction_accounts[i].index_in_caller, &callee_acct );
289 :
290 0 : fd_pubkey_t const * account_key = callee_acct.acct->pubkey;
291 0 : fd_account_meta_t const * acc_meta = fd_borrowed_account_get_acc_meta( &callee_acct );
292 :
293 : /* If the account is known and executable, we only need to consume the compute units.
294 : Executable accounts can't be modified, so we don't need to update the callee account. */
295 0 : if( fd_borrowed_account_is_executable( &callee_acct ) ) {
296 : // FIXME: should this be FD_VM_CU_MEM_UPDATE? Changing this changes the CU behaviour from main (because of the base cost)
297 0 : FD_VM_CU_UPDATE( vm, acc_meta->dlen / FD_VM_CPI_BYTES_PER_UNIT );
298 0 : continue;
299 0 : }
300 :
301 : /* FIXME: we should not need to drop the account here to avoid a double borrow.
302 : Instead, we should borrow the account before entering this function. */
303 0 : fd_borrowed_account_drop( &callee_acct );
304 :
305 : /* Find the indicies of the account in the caller and callee instructions */
306 0 : uint found = 0;
307 0 : for( ushort j=0; j<account_infos_length && !found; j++ ) {
308 0 : fd_pubkey_t const * acct_addr = account_info_keys[ j ];
309 : /* https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L912
310 : */
311 0 : if( memcmp( account_key->uc, acct_addr->uc, sizeof(fd_pubkey_t) ) != 0 ) {
312 0 : continue;
313 0 : }
314 :
315 :
316 : /* The following error is practically unreachable because
317 : essentially the same check is performed in
318 : prepare_instruction(). So a missing account error would have
319 : been returned then and there. Hence, we are skipping this
320 : duplicate check here.
321 : https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L914-L923
322 : */
323 :
324 :
325 : /* The next iteration will overwrite this if it turns out that we
326 : do not need to preserve this for update_caller().
327 : */
328 0 : fd_vm_cpi_caller_account_t * caller_account = caller_accounts + *out_len;
329 : /* Record the indicies of this account */
330 0 : ushort index_in_caller = instruction_accounts[i].index_in_caller;
331 0 : if (instruction_accounts[i].is_writable) {
332 0 : out_callee_indices[*out_len] = index_in_caller;
333 0 : out_caller_indices[*out_len] = j;
334 0 : (*out_len)++;
335 0 : }
336 0 : found = 1;
337 :
338 : /* Logically this check isn't ever going to fail due to how the
339 : account_info_keys array is set up. We replicate the check for
340 : clarity and also to guard against accidental violation of the
341 : assumed invariant in the future.
342 : https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L926-L928
343 : */
344 0 : if( FD_UNLIKELY( j >= account_infos_length ) ) {
345 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_INVALID_LENGTH );
346 0 : return FD_VM_SYSCALL_ERR_INVALID_LENGTH;
347 0 : }
348 :
349 : /* The following implements the checks in from_account_info which
350 : is invoked as do_translate() in translate_and_update_accounts()
351 : https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L931
352 : */
353 : ////// BEGIN from_account_info
354 :
355 0 : fd_vm_acc_region_meta_t * acc_region_meta = &vm->acc_region_metas[index_in_caller];
356 0 : if( FD_LIKELY( vm->direct_mapping ) ) {
357 : /* https://github.com/anza-xyz/agave/blob/v2.1.7/programs/bpf_loader/src/syscalls/cpi.rs#L116
358 : */
359 0 : ulong expected_pubkey_vaddr = serialized_pubkey_vaddr( vm, acc_region_meta );
360 : /* Max msg_sz: 40 + 18 + 18 = 76 < 127 */
361 0 : VM_SYSCALL_CPI_CHECK_ACCOUNT_INFO_POINTER_FIELD_MAX_54(vm, account_infos[j].pubkey_addr, expected_pubkey_vaddr, "key");
362 :
363 : /* https://github.com/anza-xyz/agave/blob/v2.1.7/programs/bpf_loader/src/syscalls/cpi.rs#L122
364 : */
365 0 : ulong expected_owner_vaddr = serialized_owner_vaddr( vm, acc_region_meta );
366 : /* Max msg_sz: 42 + 18 + 18 = 78 < 127 */
367 0 : VM_SYSCALL_CPI_CHECK_ACCOUNT_INFO_POINTER_FIELD_MAX_54(vm, account_infos[j].owner_addr, expected_owner_vaddr, "owner");
368 0 : }
369 :
370 : /* https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L134
371 : */
372 0 : VM_SYSCALL_CPI_ACC_INFO_LAMPORTS_VADDR( vm, (account_infos + j), lamports_vaddr );
373 0 : if( FD_LIKELY( vm->direct_mapping ) ) {
374 : /* https://github.com/anza-xyz/agave/blob/v2.1.7/programs/bpf_loader/src/syscalls/cpi.rs#L140
375 : Check that the account's lamports Rc<RefCell<&mut u64>> is
376 : not stored in the account.
377 : Because a refcell is only present if the Rust SDK is used, we
378 : only need to check this for the Rust ABI.
379 : */
380 : #ifdef VM_SYSCALL_CPI_ACC_INFO_LAMPORTS_RC_REFCELL_VADDR
381 0 : VM_SYSCALL_CPI_ACC_INFO_LAMPORTS_RC_REFCELL_VADDR( vm, (account_infos + j), lamports_rc_vaddr )
382 0 : if ( FD_UNLIKELY( lamports_rc_vaddr >= FD_VM_MEM_MAP_INPUT_REGION_START ) ) {
383 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_INVALID_POINTER );
384 0 : return FD_VM_SYSCALL_ERR_INVALID_POINTER;
385 0 : }
386 0 : #endif
387 :
388 : /* https://github.com/anza-xyz/agave/blob/v2.1.7/programs/bpf_loader/src/syscalls/cpi.rs#L144
389 : */
390 0 : ulong expected_lamports_vaddr = serialized_lamports_vaddr( vm, acc_region_meta );
391 : /* Max msg_sz: 45 + 18 + 18 = 81 < 127 */
392 0 : VM_SYSCALL_CPI_CHECK_ACCOUNT_INFO_POINTER_FIELD_MAX_54(vm, lamports_vaddr, expected_lamports_vaddr, "lamports");
393 0 : }
394 : /* https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L151
395 : */
396 0 : VM_SYSCALL_CPI_ACC_INFO_LAMPORTS( vm, (account_infos + j), lamports_haddr );
397 0 : caller_account->lamports = lamports_haddr;
398 :
399 : /* https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L154
400 : */
401 0 : caller_account->owner = FD_VM_MEM_HADDR_ST( vm, (account_infos + j)->owner_addr, alignof(uchar), sizeof(fd_pubkey_t) );
402 :
403 0 : if( FD_LIKELY( vm->direct_mapping ) ) {
404 : /* https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L161
405 : Check that the account's data Rc<RefCell<T>> is not stored in
406 : the account.
407 : Because a refcell is only present if the Rust SDK is used, we
408 : only need to check this for the Rust ABI.
409 : */
410 : #ifdef VM_SYSCALL_CPI_ACC_INFO_DATA_RC_REFCELL_VADDR
411 0 : VM_SYSCALL_CPI_ACC_INFO_DATA_RC_REFCELL_VADDR( vm, (account_infos + j), data_rc_vaddr )
412 0 : if( FD_UNLIKELY( data_rc_vaddr >= FD_VM_MEM_MAP_INPUT_REGION_START ) ) {
413 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_INVALID_POINTER );
414 0 : return FD_VM_SYSCALL_ERR_INVALID_POINTER;
415 0 : }
416 : #endif
417 0 : }
418 :
419 : /* https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L166
420 : */
421 0 : VM_SYSCALL_CPI_ACC_INFO_DATA_VADDR( vm, (account_infos + j), data_vaddr );
422 :
423 0 : if( FD_LIKELY( vm->direct_mapping ) ) {
424 : /* https://github.com/anza-xyz/agave/blob/v2.1.7/programs/bpf_loader/src/syscalls/cpi.rs#L172 */
425 0 : ulong expected_data_region_vaddr = FD_VM_MEM_MAP_INPUT_REGION_START +
426 0 : vm->input_mem_regions[acc_region_meta->region_idx].vaddr_offset;
427 : /* Max msg_sz: 41 + 18 + 18 = 77 < 127 */
428 0 : VM_SYSCALL_CPI_CHECK_ACCOUNT_INFO_POINTER_FIELD_MAX_54(vm, data_vaddr, expected_data_region_vaddr, "data");
429 0 : }
430 :
431 : /* https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L180
432 : */
433 0 : VM_SYSCALL_CPI_SET_ACC_INFO_DATA_GET_LEN( vm, (account_infos + j), data_vaddr );
434 0 : FD_VM_CU_UPDATE( vm, data_vaddr_len / FD_VM_CPI_BYTES_PER_UNIT );
435 :
436 : /* https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L187
437 : https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L331
438 : */
439 : #ifdef VM_SYSCALL_CPI_ACC_INFO_DATA_LEN_VADDR
440 : /* Rust ABI */
441 0 : VM_SYSCALL_CPI_ACC_INFO_DATA_LEN_VADDR( vm, (account_infos + j), data_len_vaddr );
442 : (void)acct_infos_va;
443 : #else
444 : /* C ABI */
445 0 : ulong data_len_vaddr = vm_syscall_cpi_data_len_vaddr_c(
446 0 : fd_ulong_sat_add( acct_infos_va, fd_ulong_sat_mul( j, VM_SYSCALL_CPI_ACC_INFO_SIZE ) ),
447 : (ulong)&((account_infos + j)->data_sz),
448 : (ulong)(account_infos + j)
449 : );
450 : #endif
451 0 : if( FD_LIKELY( vm->direct_mapping ) ) {
452 : #ifdef VM_SYSCALL_CPI_ACC_INFO_DATA_LEN_VADDR
453 : /* https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L193
454 : */
455 0 : if( FD_UNLIKELY( data_len_vaddr >= FD_VM_MEM_MAP_INPUT_REGION_START ) ) {
456 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_INVALID_POINTER );
457 0 : return FD_VM_SYSCALL_ERR_INVALID_POINTER;
458 0 : }
459 0 : #endif
460 0 : caller_account->ref_to_len_in_vm.vaddr = data_len_vaddr;
461 0 : }
462 0 : if( FD_UNLIKELY( !vm->direct_mapping ) ) {
463 0 : ulong * data_len = FD_VM_MEM_HADDR_ST(
464 0 : vm,
465 0 : data_len_vaddr,
466 0 : 1UL,
467 0 : sizeof(ulong)
468 0 : );
469 0 : caller_account->ref_to_len_in_vm.translated = data_len;
470 0 : }
471 0 : caller_account->vm_data_vaddr = data_vaddr;
472 :
473 : /* https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L228
474 : */
475 0 : caller_account->serialized_data = NULL;
476 0 : if( FD_UNLIKELY( !vm->direct_mapping ) ) {
477 0 : VM_SYSCALL_CPI_ACC_INFO_DATA( vm, (account_infos + j), data_haddr );
478 0 : (void)data_haddr_vm_addr;
479 0 : caller_account->serialized_data = data_haddr;
480 0 : caller_account->serialized_data_len = data_haddr_len;
481 0 : }
482 :
483 0 : caller_account->orig_data_len = acc_region_meta->original_data_len;
484 :
485 : ////// END from_account_info
486 :
487 : // TODO We should be able to cache the results of translation and reuse them in the update function.
488 : /* Update the callee account to reflect any changes the caller has made */
489 0 : int err = VM_SYCALL_CPI_UPDATE_CALLEE_ACC_FUNC( vm, caller_account, (uchar)index_in_caller );
490 0 : if( FD_UNLIKELY( err ) ) {
491 : /* errors are propagated in the function itself. */
492 0 : return err;
493 0 : }
494 0 : }
495 :
496 0 : if( !found ) {
497 : /* https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L966 */
498 0 : FD_BASE58_ENCODE_32_BYTES( account_key->uc, id_b58 );
499 0 : fd_log_collector_msg_many( vm->instr_ctx, 2, "Instruction references an unknown account ", 42UL, id_b58, id_b58_len );
500 0 : FD_VM_ERR_FOR_LOG_INSTR( vm, FD_EXECUTOR_INSTR_ERR_MISSING_ACC );
501 0 : return FD_EXECUTOR_INSTR_ERR_MISSING_ACC;
502 0 : }
503 0 : }
504 :
505 0 : return FD_VM_SUCCESS;
506 0 : }
507 :
508 : /* fd_vm_cpi_update_caller_acc_{rust/c} mirrors the behaviour of
509 : solana_bpf_loader_program::syscalls::cpi::update_caller_account:
510 : https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/programs/bpf_loader/src/syscalls/cpi.rs#L1291
511 :
512 : This method should be called after a CPI instruction execution has
513 : returned. It updates the given caller account info with any changes the callee
514 : has made to this account during execution, so that those changes are
515 : reflected in the rest of the caller's execution.
516 :
517 : Those changes will be in the instructions borrowed accounts cache.
518 :
519 : Paramaters:
520 : - vm: handle to the vm
521 : - caller_acc_info: caller account info object, which should be updated
522 : - pubkey: pubkey of the account
523 :
524 : TODO: error codes
525 : */
526 0 : #define VM_SYSCALL_CPI_UPDATE_CALLER_ACC_FUNC FD_EXPAND_THEN_CONCAT2(fd_vm_cpi_update_caller_acc_, VM_SYSCALL_CPI_ABI)
527 : static int
528 : VM_SYSCALL_CPI_UPDATE_CALLER_ACC_FUNC( fd_vm_t * vm,
529 : VM_SYSCALL_CPI_ACC_INFO_T const * caller_acc_info,
530 : fd_vm_cpi_caller_account_t const * caller_account,
531 : uchar instr_acc_idx,
532 0 : fd_pubkey_t const * pubkey ) {
533 0 : int err;
534 :
535 0 : if( !vm->direct_mapping ) {
536 : /* Look up the borrowed account from the instruction context, which will contain
537 : the callee's changes.
538 : TODO: Agave borrows before entering this function. We should consider doing the same.
539 : https://github.com/anza-xyz/agave/blob/v2.1.14/programs/bpf_loader/src/syscalls/cpi.rs#L1168-L1169 */
540 0 : fd_guarded_borrowed_account_t borrowed_callee_acc;
541 0 : err = fd_exec_instr_ctx_try_borrow_instr_account_with_key( vm->instr_ctx, pubkey, &borrowed_callee_acc );
542 0 : if( FD_UNLIKELY( err && ( err != FD_ACC_MGR_ERR_UNKNOWN_ACCOUNT ) ) ) {
543 0 : return 1;
544 0 : }
545 :
546 0 : fd_txn_account_t * callee_acc = borrowed_callee_acc.acct;
547 : /* Update the caller account lamports with the value from the callee */
548 0 : *(caller_account->lamports) = callee_acc->vt->get_lamports( callee_acc );
549 :
550 : /* Update the caller account owner with the value from the callee */
551 0 : fd_pubkey_t const * updated_owner = callee_acc->vt->get_owner( callee_acc );
552 0 : if( updated_owner ) *caller_account->owner = *updated_owner;
553 0 : else fd_memset( caller_account->owner, 0, sizeof(fd_pubkey_t) );
554 :
555 : /* Update the caller account data with the value from the callee */
556 0 : VM_SYSCALL_CPI_ACC_INFO_DATA( vm, caller_acc_info, caller_acc_data );
557 :
558 0 : ulong const updated_data_len = callee_acc->vt->get_data_len( callee_acc );
559 0 : if( !updated_data_len ) fd_memset( (void*)caller_acc_data, 0, caller_acc_data_len );
560 0 : ulong * ref_to_len = caller_account->ref_to_len_in_vm.translated;
561 0 : if( *ref_to_len != updated_data_len ) {
562 0 : ulong max_increase = (vm->direct_mapping && vm->is_deprecated) ? 0UL : MAX_PERMITTED_DATA_INCREASE;
563 : // https://github.com/anza-xyz/agave/blob/7f3a6cf6d3c2dcc81bb38e49a5c9ef998a6f4dd9/programs/bpf_loader/src/syscalls/cpi.rs#L1387-L1397
564 0 : if( FD_UNLIKELY( updated_data_len>fd_ulong_sat_add( caller_account->orig_data_len, max_increase ) ) ) {
565 0 : FD_VM_ERR_FOR_LOG_INSTR( vm, FD_EXECUTOR_INSTR_ERR_INVALID_REALLOC);
566 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_REALLOC;
567 0 : }
568 :
569 : /* FIXME: do we need to zero the memory that was previously used, if the new data_len is smaller?
570 : https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/programs/bpf_loader/src/syscalls/cpi.rs#L1361
571 : I don't think we do but need to double-check. */
572 :
573 : /* https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L1453
574 : */
575 0 : caller_acc_data = FD_VM_MEM_SLICE_HADDR_ST( vm, caller_acc_data_vm_addr, alignof(uchar), updated_data_len );
576 :
577 0 : *ref_to_len = updated_data_len;
578 :
579 : /* Update the serialized len field
580 : https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/programs/bpf_loader/src/syscalls/cpi.rs#L1437 */
581 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) );
582 0 : *caller_len = updated_data_len;
583 :
584 : /* FIXME return instruction error account data size too small in the same scenarios solana does
585 : https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/programs/bpf_loader/src/syscalls/cpi.rs#L1534 */
586 0 : }
587 :
588 0 : fd_memcpy( caller_acc_data, callee_acc->vt->get_data( callee_acc ), updated_data_len );
589 0 : } else { /* Direct mapping enabled */
590 :
591 : /* Look up the borrowed account from the instruction context, which will
592 : contain the callee's changes.
593 : TODO: Agave borrows before entering this function. We should consider doing the same.
594 : https://github.com/anza-xyz/agave/blob/v2.1.14/programs/bpf_loader/src/syscalls/cpi.rs#L1168-L1169 */
595 0 : fd_guarded_borrowed_account_t borrowed_callee_acc;
596 0 : err = fd_exec_instr_ctx_try_borrow_instr_account_with_key( vm->instr_ctx, pubkey, &borrowed_callee_acc );
597 0 : if( FD_UNLIKELY( err && ( err != FD_ACC_MGR_ERR_UNKNOWN_ACCOUNT ) ) ) {
598 0 : return 1;
599 0 : }
600 :
601 0 : fd_txn_account_t * callee_acc = borrowed_callee_acc.acct;
602 :
603 : /* Update the caller account lamports with the value from the callee */
604 0 : *(caller_account->lamports) = callee_acc->vt->get_lamports( callee_acc );
605 :
606 : /* Update the caller account owner with the value from the callee */
607 0 : fd_pubkey_t const * updated_owner = callee_acc->vt->get_owner( callee_acc );
608 0 : if( updated_owner ) {
609 0 : *caller_account->owner = *updated_owner;
610 0 : } else {
611 0 : fd_memset( caller_account->owner, 0, sizeof(fd_pubkey_t) );
612 0 : }
613 :
614 : /* Make sure that the capacity of the borrowed account is sized up in case
615 : it was shrunk in the CPI. It needs to be sized up in order to fit within
616 : the originally delinated regions when the account data was serialized.
617 : https://github.com/anza-xyz/agave/blob/36323b6dcd3e29e4d6fe6d73d716a3f33927148b/programs/bpf_loader/src/syscalls/cpi.rs#L1311 */
618 0 : VM_SYSCALL_CPI_ACC_INFO_METADATA( vm, caller_acc_info, caller_acc_data );
619 0 : ulong original_len = caller_account->orig_data_len;
620 :
621 0 : uchar zero_all_mapped_spare_capacity = 0;
622 : /* This case can only be triggered if the original length is more than 0 */
623 0 : if( callee_acc->vt->get_data_len( callee_acc ) < original_len ) {
624 0 : ulong new_len = callee_acc->vt->get_data_len( callee_acc );
625 : /* Allocate into the buffer to make sure that the original data len
626 : is still valid but don't change the dlen. Zero out the rest of the
627 : memory which is not used. */
628 0 : callee_acc->vt->resize( callee_acc, original_len );
629 0 : callee_acc->vt->set_data_len( callee_acc, new_len );
630 0 : zero_all_mapped_spare_capacity = 1;
631 0 : }
632 :
633 : /* Update the account data region if an account data region exists. We
634 : know that one exists iff the original len was non-zero. */
635 0 : ulong acc_region_idx = vm->acc_region_metas[instr_acc_idx].region_idx;
636 0 : if( original_len && vm->input_mem_regions[ acc_region_idx ].haddr!=(ulong)callee_acc->vt->get_data_mut( callee_acc ) ) {
637 0 : vm->input_mem_regions[ acc_region_idx ].haddr = (ulong)callee_acc->vt->get_data_mut( callee_acc );
638 0 : zero_all_mapped_spare_capacity = 1;
639 0 : }
640 :
641 0 : ulong prev_len = caller_acc_data_len;
642 0 : ulong post_len = callee_acc->vt->get_data_len( callee_acc );
643 :
644 : /* Do additional handling in the case where the data size has changed in
645 : the course of the callee's CPI. */
646 0 : if( prev_len!=post_len ) {
647 : /* There is an illegal data overflow if the post len is greater than the
648 : original data len + the max resizing limit (10KiB). Can't resize the
649 : account if the deprecated loader is being used */
650 0 : ulong max_increase = vm->is_deprecated ? 0UL : MAX_PERMITTED_DATA_INCREASE;
651 0 : if( FD_UNLIKELY( post_len>fd_ulong_sat_add( (ulong)original_len, max_increase ) ) ) {
652 0 : FD_VM_ERR_FOR_LOG_INSTR( vm, FD_EXECUTOR_INSTR_ERR_INVALID_REALLOC);
653 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_REALLOC;
654 0 : }
655 : /* There is additonal handling in the case where the account is larger
656 : than it was previously, but it has still grown since it was initially
657 : serialized. To handle this, we need to just zero out the now unused
658 : space in the account resizing region. */
659 0 : if( post_len<prev_len && prev_len>original_len ) {
660 0 : ulong dirty_realloc_start = fd_ulong_max( post_len, original_len );
661 0 : ulong dirty_realloc_len = fd_ulong_sat_sub( prev_len, dirty_realloc_start );
662 : /* We don't have to worry about multiple region writes here since we
663 : know the amount to zero out is located in the account's data
664 : resizing region. We intentionally write to the pointer despite
665 : loading it in because we assume that the permissions were changed
666 : in the callee. */
667 0 : uchar * dirty_region = FD_VM_MEM_HADDR_ST_WRITE_UNCHECKED( vm, caller_acc_data_vm_addr + dirty_realloc_start,
668 0 : alignof(uchar), dirty_realloc_len );
669 0 : fd_memset( dirty_region, 0, dirty_realloc_len );
670 0 : }
671 :
672 : /* Because the account data length changed from before to after the
673 : CPI we must update the fields appropriately. */
674 0 : ulong * ref_to_len = FD_VM_MEM_HADDR_ST( vm, caller_account->ref_to_len_in_vm.vaddr, alignof(ulong), sizeof(ulong) );
675 0 : *ref_to_len = post_len;
676 0 : ulong * serialized_len_ptr = FD_VM_MEM_HADDR_ST( vm, fd_ulong_sat_sub( caller_acc_data_vm_addr, sizeof(ulong) ), alignof(ulong), sizeof(ulong) );
677 0 : *serialized_len_ptr = post_len;
678 0 : }
679 :
680 : /* We need to zero out the end of the account data buffer if the account
681 : shrunk in size. This is because the bytes are accessible from within
682 : the VM but should be equal to zero to prevent undefined behavior. If
683 : prev_len > post_len, then dlen should be equal to original_len. */
684 0 : ulong spare_len = fd_ulong_sat_sub( fd_ulong_if( zero_all_mapped_spare_capacity, original_len, prev_len ), post_len );
685 0 : if( FD_UNLIKELY( spare_len ) ) {
686 0 : if( callee_acc->vt->get_data_len( callee_acc )>spare_len ) {
687 0 : memset( callee_acc->vt->get_data_mut( callee_acc ) + callee_acc->vt->get_data_len( callee_acc ) - spare_len, 0, spare_len );
688 0 : }
689 0 : }
690 :
691 0 : ulong realloc_bytes_used = fd_ulong_sat_sub( post_len, original_len );
692 0 : if( realloc_bytes_used && !vm->is_deprecated ) {
693 : /* We intentionally do a load in the case where we are writing to because
694 : we want to ignore the write checks. We load from the first byte of the
695 : resizing region */
696 0 : ulong resizing_idx = vm->acc_region_metas[ instr_acc_idx ].region_idx;
697 0 : if( vm->acc_region_metas[ instr_acc_idx ].has_data_region ) {
698 0 : resizing_idx++;
699 0 : }
700 0 : uchar * to_slice = (uchar*)vm->input_mem_regions[ resizing_idx ].haddr;
701 0 : uchar * from_slice = callee_acc->vt->get_data_mut( callee_acc ) + original_len;
702 :
703 0 : fd_memcpy( to_slice, from_slice, realloc_bytes_used );
704 0 : }
705 0 : }
706 :
707 0 : return FD_VM_SUCCESS;
708 0 : }
709 :
710 : /* fd_vm_syscall_cpi_{rust/c} is the entrypoint for the sol_invoke_signed_{rust/c} syscalls.
711 :
712 : The bulk of the high-level logic mirrors Solana's cpi_common entrypoint function at
713 : https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/programs/bpf_loader/src/syscalls/cpi.rs#L1060
714 : The only differences should be in the order of the error checks, which does not affect consensus.
715 :
716 : 100-foot flow:
717 : - Translate the CPI ABI structures to the FD runtime's instruction format
718 : - Update the callee accounts with any changes made by the caller prior to this CPI instruction
719 : - Dispatch the instruction to the FD runtime (actually making the CPI call)
720 : - Update the caller accounts with any changes made by the callee during CPI execution
721 :
722 : Paramaters:
723 : - vm: pointer to the virtual machine handle
724 : - instruction_va: vm address of the instruction to execute, which will be in the language-specific ABI format.
725 : - acct_infos_va: vm address of the account infos, which will be in the language-specific ABI format.
726 : - acct_info_cnt: number of account infos
727 : - signers_seeds_va: vm address of the signers seeds
728 : - signers_seeds_cnt: number of signers seeds
729 : - _ret: pointer to the return value
730 : */
731 : #define VM_SYSCALL_CPI_ENTRYPOINT FD_EXPAND_THEN_CONCAT2(fd_vm_syscall_cpi_, VM_SYSCALL_CPI_ABI)
732 : int
733 : VM_SYSCALL_CPI_ENTRYPOINT( void * _vm,
734 : ulong instruction_va,
735 : ulong acct_infos_va,
736 : ulong acct_info_cnt,
737 : ulong signers_seeds_va,
738 : ulong signers_seeds_cnt,
739 0 : ulong * _ret ) {
740 :
741 0 : fd_vm_t * vm = (fd_vm_t *)_vm;
742 :
743 0 : FD_VM_CU_UPDATE( vm, FD_VM_INVOKE_UNITS );
744 :
745 : /* Translate instruction ********************************************/
746 : /* translate_instruction is the first thing that agave does
747 : https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/programs/bpf_loader/src/syscalls/cpi.rs#L1089 */
748 :
749 : /* Translating the CPI instruction
750 : https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/programs/bpf_loader/src/syscalls/cpi.rs#L420-L424 */
751 0 : VM_SYSCALL_CPI_INSTR_T const * cpi_instruction =
752 0 : FD_VM_MEM_HADDR_LD( vm, instruction_va, VM_SYSCALL_CPI_INSTR_ALIGN, VM_SYSCALL_CPI_INSTR_SIZE );
753 :
754 : /* This needs to be here for the C ABI
755 : https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L655
756 : */
757 0 : fd_pubkey_t const * program_id = (fd_pubkey_t *)VM_SYSCALL_CPI_INSTR_PROGRAM_ID( vm, cpi_instruction );
758 :
759 : /* Translate CPI account metas *************************************************/
760 0 : VM_SYSCALL_CPI_ACC_META_T const * cpi_account_metas =
761 0 : FD_VM_MEM_SLICE_HADDR_LD( vm, VM_SYSCALL_CPI_INSTR_ACCS_ADDR( cpi_instruction ),
762 0 : VM_SYSCALL_CPI_ACC_META_ALIGN,
763 0 : fd_ulong_sat_mul( VM_SYSCALL_CPI_INSTR_ACCS_LEN( cpi_instruction ), VM_SYSCALL_CPI_ACC_META_SIZE ) );
764 :
765 : /* Translate instruction data *************************************************/
766 :
767 0 : uchar const * data = FD_VM_MEM_SLICE_HADDR_LD(
768 0 : vm, VM_SYSCALL_CPI_INSTR_DATA_ADDR( cpi_instruction ),
769 0 : FD_VM_ALIGN_RUST_U8,
770 0 : VM_SYSCALL_CPI_INSTR_DATA_LEN( cpi_instruction ));
771 :
772 :
773 : /* Instruction checks ***********************************************/
774 :
775 0 : 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 ) );
776 0 : if( FD_UNLIKELY( err ) ) {
777 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, err );
778 0 : return err;
779 0 : }
780 :
781 : /* Agave consumes CU in translate_instruction
782 : https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/programs/bpf_loader/src/syscalls/cpi.rs#L445 */
783 0 : if( FD_FEATURE_ACTIVE_BANK( vm->instr_ctx->txn_ctx->bank, loosen_cpi_size_restriction ) ) {
784 0 : FD_VM_CU_UPDATE( vm, VM_SYSCALL_CPI_INSTR_DATA_LEN( cpi_instruction ) / FD_VM_CPI_BYTES_PER_UNIT );
785 0 : }
786 :
787 : /* Final checks for translate_instruction
788 : */
789 0 : for( ulong i=0UL; i<VM_SYSCALL_CPI_INSTR_ACCS_LEN( cpi_instruction ); i++ ) {
790 0 : VM_SYSCALL_CPI_ACC_META_T const * cpi_acct_meta = &cpi_account_metas[i];
791 0 : if( FD_UNLIKELY( cpi_acct_meta->is_signer > 1U || cpi_acct_meta->is_writable > 1U ) ) {
792 : /* https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L471
793 : https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L698
794 : */
795 0 : FD_VM_ERR_FOR_LOG_INSTR( vm, FD_EXECUTOR_INSTR_ERR_INVALID_ARG );
796 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ARG;
797 0 : }
798 : /* https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L700
799 : */
800 0 : (void)VM_SYSCALL_CPI_ACC_META_PUBKEY( vm, cpi_acct_meta );
801 0 : }
802 :
803 : /* Derive PDA signers ************************************************/
804 0 : fd_pubkey_t signers[ FD_CPI_MAX_SIGNER_CNT ] = {0};
805 0 : fd_pubkey_t * caller_program_id = &vm->instr_ctx->txn_ctx->account_keys[ vm->instr_ctx->instr->program_id ];
806 : /* This is the equivalent of translate_slice in translate_signers:
807 : https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/programs/bpf_loader/src/syscalls/cpi.rs#L595 */
808 0 : if( FD_LIKELY( signers_seeds_cnt > 0UL ) ) {
809 0 : fd_vm_vec_t const * signers_seeds = FD_VM_MEM_SLICE_HADDR_LD( vm, signers_seeds_va, FD_VM_ALIGN_RUST_SLICE_U8_REF, fd_ulong_sat_mul( signers_seeds_cnt, FD_VM_VEC_SIZE ) );
810 : /* Right after translating, Agave checks against MAX_SIGNERS:
811 : https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/programs/bpf_loader/src/syscalls/cpi.rs#L602 */
812 0 : if( FD_UNLIKELY( signers_seeds_cnt > FD_CPI_MAX_SIGNER_CNT ) ) {
813 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_TOO_MANY_SIGNERS );
814 0 : return FD_VM_SYSCALL_ERR_TOO_MANY_SIGNERS;
815 0 : }
816 :
817 0 : for( ulong i=0UL; i<signers_seeds_cnt; i++ ) {
818 :
819 : /* This function will precompute the memory translation required and do
820 : some preflight checks. */
821 0 : void const * signer_seed_haddrs[ FD_VM_PDA_SEEDS_MAX ];
822 0 : ulong signer_seed_lens [ FD_VM_PDA_SEEDS_MAX ];
823 :
824 0 : int err = fd_vm_translate_and_check_program_address_inputs( vm,
825 0 : signers_seeds[i].addr,
826 0 : signers_seeds[i].len,
827 0 : 0UL,
828 0 : signer_seed_haddrs,
829 0 : signer_seed_lens ,
830 0 : NULL,
831 0 : 0U );
832 0 : if( FD_UNLIKELY( err ) ) {
833 0 : return err;
834 0 : }
835 :
836 0 : err = fd_vm_derive_pda( vm, caller_program_id, signer_seed_haddrs, signer_seed_lens, signers_seeds[i].len, NULL, &signers[i] );
837 0 : if( FD_UNLIKELY( err ) ) {
838 0 : FD_TXN_PREPARE_ERR_OVERWRITE( vm->instr_ctx->txn_ctx );
839 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_BAD_SEEDS );
840 0 : return FD_VM_SYSCALL_ERR_BAD_SEEDS;
841 0 : }
842 0 : }
843 0 : }
844 :
845 : /* Create the instruction to execute (in the input format the FD runtime expects) from
846 : the translated CPI ABI inputs. */
847 0 : fd_pubkey_t cpi_instr_acct_keys[ FD_INSTR_ACCT_MAX ];
848 0 : fd_instr_info_t * instruction_to_execute = &vm->instr_ctx->txn_ctx->cpi_instr_infos[ vm->instr_ctx->txn_ctx->cpi_instr_info_cnt++ ];
849 :
850 0 : err = VM_SYSCALL_CPI_INSTRUCTION_TO_INSTR_FUNC( vm, cpi_instruction, cpi_account_metas, program_id, data, instruction_to_execute, cpi_instr_acct_keys );
851 0 : if( FD_UNLIKELY( err ) ) {
852 0 : return err;
853 0 : }
854 :
855 : /* Prepare the instruction for execution in the runtime. This is required by the runtime
856 : before we can pass an instruction to the executor. */
857 0 : fd_instruction_account_t instruction_accounts[256];
858 0 : ulong instruction_accounts_cnt;
859 0 : err = fd_vm_prepare_instruction( instruction_to_execute, vm->instr_ctx, program_id, cpi_instr_acct_keys, instruction_accounts, &instruction_accounts_cnt, signers, signers_seeds_cnt );
860 : /* Errors are propagated in the function itself. */
861 0 : if( FD_UNLIKELY( err ) ) return err;
862 :
863 : /* Authorized program check *************************************************/
864 :
865 0 : if( FD_UNLIKELY( fd_vm_syscall_cpi_check_authorized_program( program_id, vm->instr_ctx->txn_ctx, data, VM_SYSCALL_CPI_INSTR_DATA_LEN( cpi_instruction ) ) ) ) {
866 : /* https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/programs/bpf_loader/src/syscalls/cpi.rs#L1054 */
867 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_PROGRAM_NOT_SUPPORTED );
868 0 : return FD_VM_SYSCALL_ERR_PROGRAM_NOT_SUPPORTED;
869 0 : }
870 :
871 : /* Translate account infos ******************************************/
872 : /* Direct mapping check
873 : https://github.com/anza-xyz/agave/blob/v2.1.0/programs/bpf_loader/src/syscalls/cpi.rs#L805-L814 */
874 0 : ulong acc_info_total_sz = fd_ulong_sat_mul( acct_info_cnt, VM_SYSCALL_CPI_ACC_INFO_SIZE );
875 0 : if( FD_UNLIKELY( vm->direct_mapping && fd_ulong_sat_add( acct_infos_va, acc_info_total_sz ) >= FD_VM_MEM_MAP_INPUT_REGION_START ) ) {
876 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_INVALID_POINTER );
877 0 : return FD_VM_SYSCALL_ERR_INVALID_POINTER;
878 0 : }
879 :
880 : /* This is the equivalent of translate_slice in translate_account_infos:
881 : https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/programs/bpf_loader/src/syscalls/cpi.rs#L816 */
882 0 : VM_SYSCALL_CPI_ACC_INFO_T const * acc_infos = FD_VM_MEM_SLICE_HADDR_LD( vm, acct_infos_va, VM_SYSCALL_CPI_ACC_INFO_ALIGN, acc_info_total_sz );
883 :
884 : /* Right after translating, Agave checks the number of account infos:
885 : https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/programs/bpf_loader/src/syscalls/cpi.rs#L822 */
886 0 : if( FD_FEATURE_ACTIVE_BANK( vm->instr_ctx->txn_ctx->bank, loosen_cpi_size_restriction ) ) {
887 0 : if( FD_UNLIKELY( acct_info_cnt > get_cpi_max_account_infos( vm->instr_ctx->txn_ctx ) ) ) {
888 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_MAX_INSTRUCTION_ACCOUNT_INFOS_EXCEEDED );
889 0 : return FD_VM_SYSCALL_ERR_MAX_INSTRUCTION_ACCOUNT_INFOS_EXCEEDED;
890 0 : }
891 0 : } else {
892 0 : ulong adjusted_len = fd_ulong_sat_mul( acct_info_cnt, sizeof( fd_pubkey_t ) );
893 0 : if ( FD_UNLIKELY( adjusted_len > FD_VM_MAX_CPI_INSTRUCTION_SIZE ) ) {
894 : /* "Cap the number of account_infos a caller can pass to approximate
895 : maximum that accounts that could be passed in an instruction" */
896 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_TOO_MANY_ACCOUNTS );
897 0 : return FD_VM_SYSCALL_ERR_TOO_MANY_ACCOUNTS;
898 0 : }
899 0 : }
900 :
901 0 : fd_pubkey_t const * acct_info_keys[ FD_CPI_MAX_ACCOUNT_INFOS ];
902 0 : for( ulong acct_idx = 0UL; acct_idx < acct_info_cnt; acct_idx++ ) {
903 : /* Translate each pubkey address specified in account_infos.
904 : Failed translation should lead to an access violation and
905 : implies that obviously bad account_info has been supplied.
906 : https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L833-L841 */
907 0 : acct_info_keys[ acct_idx ] = FD_VM_MEM_HADDR_LD( vm, acc_infos[ acct_idx ].pubkey_addr, alignof(uchar), sizeof(fd_pubkey_t) );
908 0 : }
909 :
910 : /* Update the callee accounts with any changes made by the caller prior to this CPI execution */
911 0 : fd_vm_cpi_caller_account_t caller_accounts[ 256 ];
912 0 : ushort callee_account_keys[256];
913 0 : ushort caller_accounts_to_update[256];
914 0 : ulong caller_accounts_to_update_len = 0;
915 0 : err = VM_SYSCALL_CPI_TRANSLATE_AND_UPDATE_ACCOUNTS_FUNC(
916 0 : vm,
917 0 : instruction_accounts,
918 0 : instruction_accounts_cnt,
919 0 : acct_infos_va,
920 0 : acct_info_keys,
921 0 : acc_infos,
922 0 : acct_info_cnt,
923 0 : callee_account_keys,
924 0 : caller_accounts_to_update,
925 0 : caller_accounts,
926 0 : &caller_accounts_to_update_len
927 0 : );
928 : /* errors are propagated in the function itself. */
929 0 : if( FD_UNLIKELY( err ) ) return err;
930 :
931 : /* Set the transaction compute meter to be the same as the VM's compute meter,
932 : so that the callee cannot use compute units that the caller has already used. */
933 0 : vm->instr_ctx->txn_ctx->compute_budget_details.compute_meter = vm->cu;
934 :
935 : /* Execute the CPI instruction in the runtime */
936 0 : int err_exec = fd_execute_instr( vm->instr_ctx->txn_ctx, instruction_to_execute );
937 0 : ulong instr_exec_res = (ulong)err_exec;
938 :
939 : /* Set the CU meter to the instruction context's transaction context's compute meter,
940 : so that the caller can't use compute units that the callee has already used. */
941 0 : vm->cu = vm->instr_ctx->txn_ctx->compute_budget_details.compute_meter;
942 :
943 0 : *_ret = instr_exec_res;
944 :
945 : /* Errors are propagated in fd_execute_instr. */
946 0 : if( FD_UNLIKELY( err_exec ) ) return err_exec;
947 :
948 : /* https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/programs/bpf_loader/src/syscalls/cpi.rs#L1128-L1145 */
949 : /* Update all account permissions before updating the account data updates.
950 : We have inlined the anza function update_caller_account_perms here.
951 : TODO: consider factoring this out */
952 0 : if( vm->direct_mapping ) {
953 0 : for( ulong i=0UL; i<caller_accounts_to_update_len; i++ ) {
954 0 : ushort acc_instr_idx = callee_account_keys[i];
955 :
956 : /* https://github.com/firedancer-io/solana/blob/508f325e19c0fd8e16683ea047d7c1a85f127e74/programs/bpf_loader/src/syscalls/cpi.rs#L939-L943 */
957 : /* Anza only even attemps to update the account permissions if it is a
958 : "caller account". Only writable accounts are caller accounts. */
959 0 : if( fd_instr_acc_is_writable_idx( vm->instr_ctx->instr, acc_instr_idx ) ) {
960 :
961 : /* Borrow the callee account
962 : https://github.com/anza-xyz/agave/blob/v2.1.14/programs/bpf_loader/src/syscalls/cpi.rs#L1154-L1155 */
963 0 : fd_guarded_borrowed_account_t callee_acc;
964 0 : FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( vm->instr_ctx, acc_instr_idx, &callee_acc );
965 :
966 : /* https://github.com/anza-xyz/agave/blob/v2.1.14/programs/bpf_loader/src/syscalls/cpi.rs#L1298 */
967 0 : uchar is_writable = !!fd_borrowed_account_can_data_be_changed( &callee_acc, &err );
968 : /* Lookup memory regions for the account data and the realloc region. */
969 0 : ulong data_region_idx = vm->acc_region_metas[ acc_instr_idx ].has_data_region ? vm->acc_region_metas[ acc_instr_idx ].region_idx : 0;
970 0 : ulong realloc_region_idx = vm->acc_region_metas[ acc_instr_idx ].has_resizing_region ? vm->acc_region_metas[ acc_instr_idx ].region_idx : 0;
971 0 : if( data_region_idx && realloc_region_idx ) {
972 0 : realloc_region_idx++;
973 0 : }
974 :
975 0 : if( data_region_idx ) {
976 0 : vm->input_mem_regions[ data_region_idx ].is_writable = is_writable;
977 0 : }
978 0 : if( FD_LIKELY( realloc_region_idx ) ) { /* Unless is deprecated loader */
979 0 : vm->input_mem_regions[ realloc_region_idx ].is_writable = is_writable;
980 0 : }
981 0 : }
982 0 : }
983 0 : }
984 :
985 : /* Update the caller accounts with any changes made by the callee during CPI execution */
986 0 : for( ulong i=0UL; i<caller_accounts_to_update_len; i++ ) {
987 : /* https://github.com/firedancer-io/solana/blob/508f325e19c0fd8e16683ea047d7c1a85f127e74/programs/bpf_loader/src/syscalls/cpi.rs#L939-L943 */
988 : /* We only want to update the writable accounts, because the non-writable
989 : caller accounts can't be changed during a CPI execution. */
990 0 : if( fd_instr_acc_is_writable_idx( vm->instr_ctx->instr, callee_account_keys[i] ) ) {
991 0 : ushort idx_in_txn = vm->instr_ctx->instr->accounts[ callee_account_keys[i] ].index_in_transaction;
992 0 : fd_pubkey_t const * callee = &vm->instr_ctx->txn_ctx->account_keys[ idx_in_txn ];
993 0 : err = VM_SYSCALL_CPI_UPDATE_CALLER_ACC_FUNC(vm, &acc_infos[ caller_accounts_to_update[i] ], caller_accounts + i, (uchar)callee_account_keys[i], callee);
994 0 : if( FD_UNLIKELY( err ) ) {
995 0 : return err;
996 0 : }
997 0 : }
998 0 : }
999 :
1000 0 : return FD_VM_SUCCESS;
1001 0 : }
1002 :
1003 : #undef VM_SYSCALL_CPI_UPDATE_CALLER_ACC_FUNC
1004 : #undef VM_SYSCALL_CPI_FROM_ACC_INFO_FUNC
1005 : #undef VM_SYSCALL_CPI_TRANSLATE_AND_UPDATE_ACCOUNTS_FUNC
1006 : #undef VM_SYSCALL_CPI_INSTRUCTION_TO_INSTR_FUNC
1007 : #undef VM_SYSCALL_CPI_FUNC
|