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/v3.0.4/syscalls/src/cpi.rs#L21-L38
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/v3.0.4/program-runtime/src/invoke_context.rs#L395-L397 */
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/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1067-L1132
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/v3.0.4/syscalls/src/cpi.rs#L817 */
139 0 : fd_guarded_borrowed_account_t callee_acc = {0};
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 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1087-L1089 */
147 0 : if( fd_borrowed_account_get_lamports( &callee_acc )!=*(caller_account->lamports) ) {
148 0 : err = fd_borrowed_account_set_lamports( &callee_acc, *(caller_account->lamports) );
149 0 : if( FD_UNLIKELY( err ) ) {
150 0 : FD_VM_ERR_FOR_LOG_INSTR( vm, err );
151 0 : return -1;
152 0 : }
153 0 : }
154 :
155 : /* With stricter_abi_and_runtime_constraints enabled, we validate account
156 : length changes and update the associated borrowed account with any
157 : changed made. If direct mapping is also enabled, we skip actually copying
158 : the data back to the borrowed account, as it is already updated in-place.
159 :
160 : https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1091-L1113 */
161 0 : if( vm->stricter_abi_and_runtime_constraints ) {
162 0 : ulong prev_len = fd_borrowed_account_get_data_len( &callee_acc );
163 0 : ulong post_len = *caller_account->ref_to_len_in_vm;
164 :
165 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1094-L1109 */
166 0 : if( FD_UNLIKELY( prev_len!=post_len ) ) {
167 0 : ulong address_space_reserved_for_account;
168 0 : if( vm->is_deprecated ) {
169 0 : address_space_reserved_for_account = caller_account->orig_data_len;
170 0 : } else {
171 0 : address_space_reserved_for_account = fd_ulong_sat_add( caller_account->orig_data_len, MAX_PERMITTED_DATA_INCREASE );
172 0 : }
173 :
174 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1103-L1105 */
175 0 : if( FD_UNLIKELY( post_len>address_space_reserved_for_account ) ) {
176 0 : FD_VM_ERR_FOR_LOG_INSTR( vm, FD_EXECUTOR_INSTR_ERR_INVALID_REALLOC );
177 0 : return -1;
178 0 : }
179 :
180 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1106 */
181 0 : err = fd_borrowed_account_set_data_length( &callee_acc, post_len );
182 0 : if( FD_UNLIKELY( err ) ) {
183 0 : FD_VM_ERR_FOR_LOG_INSTR( vm, err );
184 0 : return -1;
185 0 : }
186 0 : }
187 :
188 : /* Without direct mapping, we need to copy the account data from the VM's
189 : serialized buffer back to the borrowed account. With direct mapping,
190 : data is modified in-place so no copy is needed.
191 : https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1110-L1112 */
192 0 : int err;
193 0 : if( !vm->direct_mapping && fd_borrowed_account_can_data_be_changed( &callee_acc, &err ) ) {
194 0 : err = fd_borrowed_account_set_data_from_slice( &callee_acc, caller_account->serialized_data, caller_account->serialized_data_len );
195 0 : if( FD_UNLIKELY( err ) ) {
196 0 : FD_VM_ERR_FOR_LOG_INSTR( vm, err );
197 0 : return -1;
198 0 : }
199 0 : }
200 0 : } else {
201 : /* Direct mapping is not enabled, so we need to copy the account data
202 : from the VM's serialized buffer back to the borrowed account.
203 :
204 : https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1114-L1121 */
205 0 : int err;
206 0 : if( fd_borrowed_account_can_data_be_resized( &callee_acc, caller_account->serialized_data_len, &err ) &&
207 0 : fd_borrowed_account_can_data_be_changed( &callee_acc, &err ) ) {
208 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1116 */
209 0 : err = fd_borrowed_account_set_data_from_slice( &callee_acc, caller_account->serialized_data, caller_account->serialized_data_len );
210 0 : if( FD_UNLIKELY( err ) ) {
211 0 : FD_VM_ERR_FOR_LOG_INSTR( vm, err );
212 0 : return -1;
213 0 : }
214 0 : } else if( FD_UNLIKELY( caller_account->serialized_data_len!=fd_borrowed_account_get_data_len( &callee_acc ) ||
215 0 : memcmp( fd_borrowed_account_get_data( &callee_acc ), caller_account->serialized_data, caller_account->serialized_data_len ) ) ) {
216 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1117-L1119 */
217 0 : FD_VM_ERR_FOR_LOG_INSTR( vm, err );
218 0 : return -1;
219 0 : }
220 0 : }
221 :
222 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1124-L1129 */
223 0 : if( FD_UNLIKELY( memcmp( fd_borrowed_account_get_owner( &callee_acc ), caller_account->owner, sizeof(fd_pubkey_t) ) ) ) {
224 0 : err = fd_borrowed_account_set_owner( &callee_acc, caller_account->owner );
225 0 : if( FD_UNLIKELY( err ) ) {
226 0 : FD_VM_ERR_FOR_LOG_INSTR( vm, err );
227 0 : return -1;
228 0 : }
229 0 : }
230 :
231 0 : return FD_VM_SUCCESS;
232 0 : }
233 :
234 : /*
235 : fd_vm_syscall_cpi_translate_and_update_accounts_ mirrors the behaviour of
236 : solana_bpf_loader_program::syscalls::cpi::translate_and_update_accounts:
237 : https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L767-L892
238 :
239 : It translates the caller accounts to the host address space, and then calls
240 : fd_vm_syscall_cpi_update_callee_acc to update the callee borrowed account with any changes
241 : the caller has made to the account during execution before this CPI call.
242 :
243 : It also populates the out_callee_indices and out_caller_indices arrays:
244 : - out_callee_indices: indices of the callee accounts in the transaction
245 : - out_caller_indices: indices of the caller accounts in the account_infos array
246 :
247 : Parameters:
248 : - vm: pointer to the virtual machine handle
249 : - instruction_accounts: array of instruction accounts
250 : - instruction_accounts_cnt: length of the instruction_accounts array
251 : - account_infos: array of account infos
252 : - account_infos_length: length of the account_infos array
253 :
254 : Populates the given out_callee_indices and out_caller_indices arrays:
255 : - out_callee_indices: indices of the callee accounts in the transaction
256 : - out_caller_indices: indices of the caller accounts in the account_infos array
257 : - out_len: length of the out_callee_indices and out_caller_indices arrays
258 : */
259 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)
260 : static int
261 : VM_SYSCALL_CPI_TRANSLATE_AND_UPDATE_ACCOUNTS_FUNC(
262 : fd_vm_t * vm,
263 : fd_instruction_account_t const * instruction_accounts,
264 : ulong const instruction_accounts_cnt,
265 : ulong acct_infos_va,
266 : fd_pubkey_t const * * account_info_keys, /* same length as account_infos_length */
267 : VM_SYSCALL_CPI_ACC_INFO_T const * account_infos,
268 : ulong const account_infos_length,
269 : ushort * out_callee_indices,
270 : ushort * out_caller_indices,
271 : fd_vm_cpi_caller_account_t * caller_accounts,
272 0 : ulong * out_len ) {
273 0 : for( ulong i=0UL; i<instruction_accounts_cnt; i++ ) {
274 0 : if( i!=instruction_accounts[i].index_in_callee ) {
275 : /* Skip duplicate accounts */
276 0 : continue;
277 0 : }
278 :
279 : /* `fd_vm_prepare_instruction()` will always set up a valid index for `index_in_caller`, so we can access the borrowed account directly.
280 : A borrowed account will always have non-NULL meta (if the account doesn't exist, `fd_executor_setup_accounts_for_txn()`
281 : will set its meta up) */
282 :
283 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L817 */
284 0 : fd_guarded_borrowed_account_t callee_acct = {0};
285 0 : FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( vm->instr_ctx, instruction_accounts[i].index_in_caller, &callee_acct );
286 :
287 0 : fd_pubkey_t const * account_key = callee_acct.acct->pubkey;
288 0 : fd_account_meta_t const * acc_meta = fd_borrowed_account_get_acc_meta( &callee_acct );
289 :
290 : /* If the account is known and executable, we only need to consume the compute units.
291 : Executable accounts can't be modified, so we don't need to update the callee account. */
292 0 : if( fd_borrowed_account_is_executable( &callee_acct ) ) {
293 : // FIXME: should this be FD_VM_CU_MEM_UPDATE? Changing this changes the CU behaviour from main (because of the base cost)
294 0 : FD_VM_CU_UPDATE( vm, acc_meta->dlen / FD_VM_CPI_BYTES_PER_UNIT );
295 0 : continue;
296 0 : }
297 :
298 : /* FIXME: we should not need to drop the account here to avoid a double borrow.
299 : Instead, we should borrow the account before entering this function. */
300 0 : fd_borrowed_account_drop( &callee_acct );
301 :
302 : /* Find the indicies of the account in the caller and callee instructions */
303 0 : uint found = 0;
304 0 : for( ushort j=0; j<account_infos_length && !found; j++ ) {
305 0 : fd_pubkey_t const * acct_addr = account_info_keys[ j ];
306 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L832
307 : */
308 0 : if( memcmp( account_key->uc, acct_addr->uc, sizeof(fd_pubkey_t) ) != 0 ) {
309 0 : continue;
310 0 : }
311 :
312 : /* The next iteration will overwrite this if it turns out that we
313 : do not need to preserve this for update_caller().
314 : */
315 0 : fd_vm_cpi_caller_account_t * caller_account = caller_accounts + *out_len;
316 : /* Record the indicies of this account */
317 0 : ushort index_in_caller = instruction_accounts[i].index_in_caller;
318 0 : if (instruction_accounts[i].is_writable) {
319 0 : out_callee_indices[*out_len] = index_in_caller;
320 0 : out_caller_indices[*out_len] = j;
321 0 : (*out_len)++;
322 0 : }
323 0 : found = 1;
324 :
325 : /* Logically this check isn't ever going to fail due to how the
326 : account_info_keys array is set up. We replicate the check for
327 : clarity and also to guard against accidental violation of the
328 : assumed invariant in the future.
329 : https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L846-L849
330 : */
331 0 : if( FD_UNLIKELY( j >= account_infos_length ) ) {
332 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_INVALID_LENGTH );
333 0 : return FD_VM_SYSCALL_ERR_INVALID_LENGTH;
334 0 : }
335 :
336 : /* The following implements the checks in from_account_info which
337 : is invoked as do_translate() in translate_and_update_accounts()
338 : https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L850-L861
339 : */
340 : ////// BEGIN from_account_info
341 :
342 0 : fd_vm_acc_region_meta_t * acc_region_meta = &vm->acc_region_metas[index_in_caller];
343 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L138 */
344 0 : if( FD_LIKELY( vm->stricter_abi_and_runtime_constraints ) ) {
345 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L139-L144 */
346 0 : ulong expected_pubkey_vaddr = serialized_pubkey_vaddr( vm, acc_region_meta->region_idx );
347 : /* Max msg_sz: 40 + 18 + 18 = 76 < 127 */
348 0 : VM_SYSCALL_CPI_CHECK_ACCOUNT_INFO_POINTER_FIELD_MAX_54(vm, account_infos[j].pubkey_addr, expected_pubkey_vaddr, "key");
349 :
350 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L145-L150 */
351 0 : ulong expected_owner_vaddr = serialized_owner_vaddr( vm, acc_region_meta->region_idx );
352 : /* Max msg_sz: 42 + 18 + 18 = 78 < 127 */
353 0 : VM_SYSCALL_CPI_CHECK_ACCOUNT_INFO_POINTER_FIELD_MAX_54(vm, account_infos[j].owner_addr, expected_owner_vaddr, "owner");
354 0 : }
355 :
356 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L155-L175 */
357 0 : VM_SYSCALL_CPI_ACC_INFO_LAMPORTS_VADDR( vm, (account_infos + j), lamports_vaddr );
358 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L162-L173 */
359 0 : if( FD_LIKELY( vm->stricter_abi_and_runtime_constraints ) ) {
360 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L163-L165
361 : Check that the account's lamports Rc<RefCell<&mut u64>> is not
362 : stored in the account region. Because a refcell is only present if
363 : the Rust SDK is used, we only need to check this for the Rust ABI. */
364 : #ifdef VM_SYSCALL_CPI_ACC_INFO_LAMPORTS_RC_REFCELL_VADDR
365 0 : VM_SYSCALL_CPI_ACC_INFO_LAMPORTS_RC_REFCELL_VADDR( vm, (account_infos + j), lamports_rc_vaddr )
366 0 : if ( FD_UNLIKELY( lamports_rc_vaddr >= FD_VM_MEM_MAP_INPUT_REGION_START ) ) {
367 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_INVALID_POINTER );
368 0 : return FD_VM_SYSCALL_ERR_INVALID_POINTER;
369 0 : }
370 0 : #endif
371 :
372 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L167-L172 */
373 0 : ulong expected_lamports_vaddr = serialized_lamports_vaddr( vm, acc_region_meta->region_idx );
374 : /* Max msg_sz: 45 + 18 + 18 = 81 < 127 */
375 0 : VM_SYSCALL_CPI_CHECK_ACCOUNT_INFO_POINTER_FIELD_MAX_54(vm, lamports_vaddr, expected_lamports_vaddr, "lamports");
376 0 : }
377 :
378 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L153-L175
379 : */
380 0 : VM_SYSCALL_CPI_ACC_INFO_LAMPORTS( vm, (account_infos + j), lamports_haddr );
381 0 : caller_account->lamports = lamports_haddr;
382 :
383 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L177-L181
384 : */
385 0 : caller_account->owner = FD_VM_MEM_HADDR_ST( vm, (account_infos + j)->owner_addr, alignof(uchar), sizeof(fd_pubkey_t) );
386 :
387 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L190-L203
388 : */
389 0 : VM_SYSCALL_CPI_ACC_INFO_DATA_VADDR( vm, (account_infos + j), data_vaddr );
390 :
391 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L196-L203 */
392 0 : if( vm->stricter_abi_and_runtime_constraints ) {
393 0 : fd_vm_input_region_t * region = &vm->input_mem_regions[ acc_region_meta->region_idx ];
394 0 : ulong expected_data_vaddr = FD_VM_MEM_MAP_INPUT_REGION_START +
395 0 : region->vaddr_offset + region->address_space_reserved;
396 0 : VM_SYSCALL_CPI_CHECK_ACCOUNT_INFO_POINTER_FIELD_MAX_54(vm, data_vaddr, expected_data_vaddr, "data");
397 0 : }
398 :
399 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L205-L210
400 : */
401 0 : VM_SYSCALL_CPI_SET_ACC_INFO_DATA_GET_LEN( vm, (account_infos + j), data_vaddr );
402 0 : FD_VM_CU_UPDATE( vm, data_vaddr_len / FD_VM_CPI_BYTES_PER_UNIT );
403 :
404 : #ifdef VM_SYSCALL_CPI_ACC_INFO_DATA_LEN_VADDR
405 : /* Rust ABI
406 : https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L212-L221 */
407 0 : VM_SYSCALL_CPI_ACC_INFO_DATA_LEN_VADDR( vm, (account_infos + j), data_len_vaddr );
408 0 : if( FD_UNLIKELY( vm->stricter_abi_and_runtime_constraints && data_len_vaddr >= FD_VM_MEM_MAP_INPUT_REGION_START ) ) {
409 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_INVALID_POINTER );
410 0 : return FD_VM_SYSCALL_ERR_INVALID_POINTER;
411 0 : }
412 0 : (void)acct_infos_va;
413 : #else
414 : /* C ABI
415 : https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L310-L316 */
416 0 : ulong data_len_vaddr = vm_syscall_cpi_data_len_vaddr_c(
417 0 : fd_ulong_sat_add( acct_infos_va, fd_ulong_sat_mul( j, VM_SYSCALL_CPI_ACC_INFO_SIZE ) ),
418 : (ulong)&((account_infos + j)->data_sz),
419 : (ulong)(account_infos + j)
420 : );
421 : #endif
422 :
423 : /* Rust ABI: https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L222
424 : C ABI: https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L317 */
425 0 : ulong * data_len = FD_VM_MEM_HADDR_ST( vm, data_len_vaddr, 1UL, sizeof(ulong) );
426 0 : caller_account->ref_to_len_in_vm = data_len;
427 :
428 : /* Rust ABI: https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L226
429 : C ABI: https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L324 */
430 0 : caller_account->vm_data_vaddr = data_vaddr;
431 :
432 : /* Rust ABI: https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L224-L230
433 : C ABI: https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L302-L308
434 :
435 : Both ABIs call CallerAccount::get_serialized_data:
436 : https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L90-L123
437 :
438 : With both stricter_abi_and_runtime_constraints and direct_mapping,
439 : account data is modified in-place so we don't track the
440 : serialized_data pointer.
441 :
442 : With stricter_abi only (no direct_mapping), data was copied into the input
443 : region buffer. We don't apply the extra memory translation checks, as
444 : we have checked the data pointer is valid above. So instead we add
445 : the vaddr to the start of the input region address space - copying
446 : this logic from Agave.
447 :
448 : In legacy mode, we translate the data pointer directly, as it just
449 : maps to a location in the single input region. */
450 0 : if( vm->stricter_abi_and_runtime_constraints && vm->direct_mapping ) {
451 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L97-L99 */
452 0 : caller_account->serialized_data = NULL;
453 0 : caller_account->serialized_data_len = 0UL;
454 0 : } else if( vm->stricter_abi_and_runtime_constraints ) {
455 : /* Skip translation checks here, following the Agave logic:
456 : https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L99-L115 */
457 0 : uchar * serialization_ptr = (uchar *)FD_VM_MEM_SLICE_HADDR_ST( vm, FD_VM_MEM_MAP_INPUT_REGION_START, alignof(uchar), 1UL );
458 0 : caller_account->serialized_data = serialization_ptr + fd_ulong_sat_sub( data_vaddr, FD_VM_MEM_MAP_INPUT_REGION_START );
459 0 : caller_account->serialized_data_len = data_vaddr_len;
460 0 : } else {
461 : /* https://github.com/anza-xyz/agave/blob/v3.0.1/syscalls/src/cpi.rs#L115-L122 */
462 0 : VM_SYSCALL_CPI_ACC_INFO_DATA( vm, (account_infos + j), data_haddr );
463 0 : (void)data_haddr_vm_addr;
464 0 : caller_account->serialized_data = data_haddr;
465 0 : caller_account->serialized_data_len = data_haddr_len;
466 0 : }
467 :
468 : /* Rust ABI: https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L237
469 : C ABI: https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L322 */
470 0 : caller_account->orig_data_len = acc_region_meta->original_data_len;
471 :
472 : ////// END from_account_info
473 :
474 : // TODO We should be able to cache the results of translation and reuse them in the update function.
475 : /* Update the callee account to reflect any changes the caller has made
476 : https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L863-L873 */
477 0 : int err = VM_SYCALL_CPI_UPDATE_CALLEE_ACC_FUNC( vm, caller_account, (uchar)index_in_caller );
478 0 : if( FD_UNLIKELY( err ) ) {
479 : /* errors are propagated in the function itself. */
480 0 : return err;
481 0 : }
482 0 : }
483 :
484 0 : if( !found ) {
485 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L882-L887 */
486 0 : FD_BASE58_ENCODE_32_BYTES( account_key->uc, id_b58 );
487 0 : fd_log_collector_msg_many( vm->instr_ctx, 2, "Instruction references an unknown account ", 42UL, id_b58, id_b58_len );
488 0 : FD_VM_ERR_FOR_LOG_INSTR( vm, FD_EXECUTOR_INSTR_ERR_MISSING_ACC );
489 0 : return FD_EXECUTOR_INSTR_ERR_MISSING_ACC;
490 0 : }
491 0 : }
492 :
493 0 : return FD_VM_SUCCESS;
494 0 : }
495 :
496 : /* fd_vm_cpi_update_caller_acc_{rust/c} mirrors the behaviour of
497 : solana_bpf_loader_program::syscalls::cpi::update_caller_account:
498 : https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1171-L1268
499 :
500 : This method should be called after a CPI instruction execution has
501 : returned. It updates the given caller account info with any changes the callee
502 : has made to this account during execution, so that those changes are
503 : reflected in the rest of the caller's execution.
504 :
505 : Those changes will be in the instructions borrowed accounts cache.
506 :
507 : Paramaters:
508 : - vm: handle to the vm
509 : - caller_acc_info: caller account info object, which should be updated
510 : - pubkey: pubkey of the account
511 :
512 : TODO: error codes
513 : */
514 0 : #define VM_SYSCALL_CPI_UPDATE_CALLER_ACC_FUNC FD_EXPAND_THEN_CONCAT2(fd_vm_cpi_update_caller_acc_, VM_SYSCALL_CPI_ABI)
515 : static int
516 : VM_SYSCALL_CPI_UPDATE_CALLER_ACC_FUNC( fd_vm_t * vm,
517 : VM_SYSCALL_CPI_ACC_INFO_T const * caller_acc_info FD_FN_UNUSED,
518 : fd_vm_cpi_caller_account_t * caller_account,
519 : uchar instr_acc_idx FD_FN_UNUSED,
520 0 : fd_pubkey_t const * pubkey ) {
521 0 : int err;
522 :
523 : /* Look up the borrowed account from the instruction context, which will contain
524 : the callee's changes.
525 : TODO: Agave borrows before entering this function. We should consider doing the same.
526 : https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1033-L1034 */
527 0 : fd_guarded_borrowed_account_t borrowed_callee_acc = {0};
528 0 : err = fd_exec_instr_ctx_try_borrow_instr_account_with_key( vm->instr_ctx, pubkey, &borrowed_callee_acc );
529 0 : if( FD_UNLIKELY( err && ( err != FD_ACC_MGR_ERR_UNKNOWN_ACCOUNT ) ) ) {
530 0 : return 1;
531 0 : }
532 :
533 0 : fd_txn_account_t * callee_acc = borrowed_callee_acc.acct;
534 : /* Update the caller account lamports with the value from the callee
535 : https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1191 */
536 0 : *(caller_account->lamports) = fd_txn_account_get_lamports( callee_acc );
537 :
538 : /* Update the caller account owner with the value from the callee
539 : https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1192 */
540 0 : fd_pubkey_t const * updated_owner = fd_txn_account_get_owner( callee_acc );
541 0 : if( updated_owner ) *caller_account->owner = *updated_owner;
542 0 : else fd_memset( caller_account->owner, 0, sizeof(fd_pubkey_t) );
543 :
544 : /* Update the caller account data with the value from the callee
545 : https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1194-L1195 */
546 0 : ulong prev_len = *caller_account->ref_to_len_in_vm;
547 0 : ulong post_len = fd_txn_account_get_data_len( callee_acc );
548 :
549 : /* Calculate the address space reserved for the account. With stricter_abi_and_runtime_constraints
550 : and deprecated loader, the reserved space equals original length (no realloc space).
551 : Otherwise, we add MAX_PERMITTED_DATA_INCREASE for reallocation.
552 : https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1197-L1204 */
553 0 : ulong address_space_reserved_for_account;
554 0 : if( vm->stricter_abi_and_runtime_constraints && vm->is_deprecated ) {
555 0 : address_space_reserved_for_account = caller_account->orig_data_len;
556 0 : } else {
557 0 : address_space_reserved_for_account = fd_ulong_sat_add( caller_account->orig_data_len, MAX_PERMITTED_DATA_INCREASE );
558 0 : }
559 :
560 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1206-L1216 */
561 0 : if( post_len > address_space_reserved_for_account &&
562 0 : ( vm->stricter_abi_and_runtime_constraints || prev_len != post_len ) ) {
563 0 : ulong max_increase = fd_ulong_sat_sub( address_space_reserved_for_account, caller_account->orig_data_len );
564 0 : fd_log_collector_printf_dangerous_max_127( vm->instr_ctx, "Account data size realloc limited to %lu in inner instructions", 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 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1218-L1252 */
570 0 : if( prev_len != post_len ) {
571 :
572 : /* Without direct mapping, we need to adjust the serialized data buffer
573 : when the length changes.
574 :
575 : With direct mapping, data is mapped in-place so no buffer manipulation
576 : is needed.
577 :
578 : https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1219-L1239 */
579 0 : if( !( vm->stricter_abi_and_runtime_constraints && vm->direct_mapping ) ) {
580 :
581 : /* If the account has shrunk, zero out memory that was previously used
582 : https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1222-L1230 */
583 0 : if( post_len < prev_len ) {
584 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1227-L1228 */
585 0 : if( caller_account->serialized_data_len < post_len ) {
586 0 : FD_VM_ERR_FOR_LOG_INSTR( vm, FD_EXECUTOR_INSTR_ERR_ACC_DATA_TOO_SMALL );
587 0 : return FD_EXECUTOR_INSTR_ERR_ACC_DATA_TOO_SMALL;
588 0 : }
589 :
590 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1225-L1229 */
591 0 : fd_memset( caller_account->serialized_data + post_len, 0, caller_account->serialized_data_len - post_len );
592 0 : }
593 :
594 : /* Set caller_account.serialized_data to post_len.
595 : https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1231-L1238 */
596 0 : if( vm->stricter_abi_and_runtime_constraints ) {
597 : /* Calculate the serialized data pointer from the input region base,
598 : as described above.
599 :
600 : https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L99-L115 */
601 0 : uchar * serialization_ptr = (uchar *)FD_VM_MEM_SLICE_HADDR_ST( vm, FD_VM_MEM_MAP_INPUT_REGION_START, alignof(uchar), 1UL );
602 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1234 */
603 0 : caller_account->serialized_data = serialization_ptr + fd_ulong_sat_sub( caller_account->vm_data_vaddr, FD_VM_MEM_MAP_INPUT_REGION_START );
604 0 : caller_account->serialized_data_len = post_len;
605 0 : } else {
606 : /* Translate the data pointer directly from the VM address, if
607 : stricter_abi_and_runtime_constraints (or direct mapping) is not
608 : enabled.
609 :
610 : https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L115-L122 */
611 0 : caller_account->serialized_data = (uchar *)FD_VM_MEM_SLICE_HADDR_ST( vm, caller_account->vm_data_vaddr, alignof(uchar), post_len );
612 0 : caller_account->serialized_data_len = post_len;
613 0 : }
614 0 : }
615 :
616 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1240-L1241 */
617 0 : *caller_account->ref_to_len_in_vm = post_len;
618 :
619 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1243-L1251 */
620 0 : ulong * caller_len = FD_VM_MEM_HADDR_ST( vm, fd_ulong_sat_sub(caller_account->vm_data_vaddr, sizeof(ulong)), alignof(ulong), sizeof(ulong) );
621 0 : *caller_len = post_len;
622 0 : }
623 :
624 : /* Without direct mapping, copy the updated account data from the callee's
625 : account back to the caller's serialized data buffer. With direct mapping,
626 : data was modified in-place so no copy is needed.
627 :
628 : https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1254-L1265 */
629 0 : if( !(vm->stricter_abi_and_runtime_constraints && vm->direct_mapping) ) {
630 0 : fd_memcpy( caller_account->serialized_data, fd_txn_account_get_data( callee_acc ), post_len );
631 0 : }
632 :
633 :
634 0 : return FD_VM_SUCCESS;
635 0 : }
636 :
637 : /* fd_vm_syscall_cpi_{rust/c} is the entrypoint for the sol_invoke_signed_{rust/c} syscalls.
638 :
639 : The bulk of the high-level logic mirrors Solana's cpi_common entrypoint function at
640 : https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L964-L1065
641 : The only differences should be in the order of the error checks, which does not affect consensus.
642 :
643 : 100-foot flow:
644 : - Translate the CPI ABI structures to the FD runtime's instruction format
645 : - Update the callee accounts with any changes made by the caller prior to this CPI instruction
646 : - Dispatch the instruction to the FD runtime (actually making the CPI call)
647 : - Update the caller accounts with any changes made by the callee during CPI execution
648 :
649 : Paramaters:
650 : - vm: pointer to the virtual machine handle
651 : - instruction_va: vm address of the instruction to execute, which will be in the language-specific ABI format.
652 : - acct_infos_va: vm address of the account infos, which will be in the language-specific ABI format.
653 : - acct_info_cnt: number of account infos
654 : - signers_seeds_va: vm address of the signers seeds
655 : - signers_seeds_cnt: number of signers seeds
656 : - _ret: pointer to the return value
657 : */
658 : #define VM_SYSCALL_CPI_ENTRYPOINT FD_EXPAND_THEN_CONCAT2(fd_vm_syscall_cpi_, VM_SYSCALL_CPI_ABI)
659 : int
660 : VM_SYSCALL_CPI_ENTRYPOINT( void * _vm,
661 : ulong instruction_va,
662 : ulong acct_infos_va,
663 : ulong acct_info_cnt,
664 : ulong signers_seeds_va,
665 : ulong signers_seeds_cnt,
666 0 : ulong * _ret ) {
667 :
668 0 : fd_vm_t * vm = (fd_vm_t *)_vm;
669 :
670 0 : FD_VM_CU_UPDATE( vm, FD_VM_INVOKE_UNITS );
671 :
672 : /* Translate instruction ********************************************/
673 : /* translate_instruction is the first thing that agave does
674 : https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/programs/bpf_loader/src/syscalls/cpi.rs#L1089 */
675 :
676 : /* Translating the CPI instruction
677 : https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/programs/bpf_loader/src/syscalls/cpi.rs#L420-L424 */
678 0 : VM_SYSCALL_CPI_INSTR_T const * cpi_instruction =
679 0 : FD_VM_MEM_HADDR_LD( vm, instruction_va, VM_SYSCALL_CPI_INSTR_ALIGN, VM_SYSCALL_CPI_INSTR_SIZE );
680 :
681 : /* This needs to be here for the C ABI
682 : https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L655
683 : */
684 0 : fd_pubkey_t const * program_id = (fd_pubkey_t *)VM_SYSCALL_CPI_INSTR_PROGRAM_ID( vm, cpi_instruction );
685 :
686 : /* Translate CPI account metas *************************************************/
687 0 : VM_SYSCALL_CPI_ACC_META_T const * cpi_account_metas =
688 0 : FD_VM_MEM_SLICE_HADDR_LD( vm, VM_SYSCALL_CPI_INSTR_ACCS_ADDR( cpi_instruction ),
689 0 : VM_SYSCALL_CPI_ACC_META_ALIGN,
690 0 : fd_ulong_sat_mul( VM_SYSCALL_CPI_INSTR_ACCS_LEN( cpi_instruction ), VM_SYSCALL_CPI_ACC_META_SIZE ) );
691 :
692 : /* Translate instruction data *************************************************/
693 :
694 0 : uchar const * data = FD_VM_MEM_SLICE_HADDR_LD(
695 0 : vm, VM_SYSCALL_CPI_INSTR_DATA_ADDR( cpi_instruction ),
696 0 : FD_VM_ALIGN_RUST_U8,
697 0 : VM_SYSCALL_CPI_INSTR_DATA_LEN( cpi_instruction ));
698 :
699 :
700 : /* Instruction checks ***********************************************/
701 :
702 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 ) );
703 0 : if( FD_UNLIKELY( err ) ) {
704 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, err );
705 0 : return err;
706 0 : }
707 :
708 : /* Agave consumes CU in translate_instruction
709 : https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/programs/bpf_loader/src/syscalls/cpi.rs#L445 */
710 0 : if( FD_FEATURE_ACTIVE_BANK( vm->instr_ctx->txn_ctx->bank, loosen_cpi_size_restriction ) ) {
711 0 : FD_VM_CU_UPDATE( vm, VM_SYSCALL_CPI_INSTR_DATA_LEN( cpi_instruction ) / FD_VM_CPI_BYTES_PER_UNIT );
712 0 : }
713 :
714 : /* Final checks for translate_instruction
715 : */
716 0 : for( ulong i=0UL; i<VM_SYSCALL_CPI_INSTR_ACCS_LEN( cpi_instruction ); i++ ) {
717 0 : VM_SYSCALL_CPI_ACC_META_T const * cpi_acct_meta = &cpi_account_metas[i];
718 0 : if( FD_UNLIKELY( cpi_acct_meta->is_signer > 1U || cpi_acct_meta->is_writable > 1U ) ) {
719 : /* https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L471
720 : https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L698
721 : */
722 0 : FD_VM_ERR_FOR_LOG_INSTR( vm, FD_EXECUTOR_INSTR_ERR_INVALID_ARG );
723 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ARG;
724 0 : }
725 : /* https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L700
726 : */
727 0 : (void)VM_SYSCALL_CPI_ACC_META_PUBKEY( vm, cpi_acct_meta );
728 0 : }
729 :
730 : /* Derive PDA signers ************************************************/
731 0 : fd_pubkey_t signers[ FD_CPI_MAX_SIGNER_CNT ] = {0};
732 0 : fd_pubkey_t * caller_program_id = &vm->instr_ctx->txn_ctx->account_keys[ vm->instr_ctx->instr->program_id ];
733 : /* This is the equivalent of translate_slice in translate_signers:
734 : https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/programs/bpf_loader/src/syscalls/cpi.rs#L595 */
735 0 : if( FD_LIKELY( signers_seeds_cnt > 0UL ) ) {
736 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 ) );
737 : /* Right after translating, Agave checks against MAX_SIGNERS:
738 : https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/programs/bpf_loader/src/syscalls/cpi.rs#L602 */
739 0 : if( FD_UNLIKELY( signers_seeds_cnt > FD_CPI_MAX_SIGNER_CNT ) ) {
740 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_TOO_MANY_SIGNERS );
741 0 : return FD_VM_SYSCALL_ERR_TOO_MANY_SIGNERS;
742 0 : }
743 :
744 0 : for( ulong i=0UL; i<signers_seeds_cnt; i++ ) {
745 :
746 : /* This function will precompute the memory translation required and do
747 : some preflight checks. */
748 0 : void const * signer_seed_haddrs[ FD_VM_PDA_SEEDS_MAX ];
749 0 : ulong signer_seed_lens [ FD_VM_PDA_SEEDS_MAX ];
750 :
751 0 : int err = fd_vm_translate_and_check_program_address_inputs( vm,
752 0 : signers_seeds[i].addr,
753 0 : signers_seeds[i].len,
754 0 : 0UL,
755 0 : signer_seed_haddrs,
756 0 : signer_seed_lens ,
757 0 : NULL,
758 0 : 0U );
759 0 : if( FD_UNLIKELY( err ) ) {
760 0 : return err;
761 0 : }
762 :
763 0 : err = fd_vm_derive_pda( vm, caller_program_id, signer_seed_haddrs, signer_seed_lens, signers_seeds[i].len, NULL, &signers[i] );
764 0 : if( FD_UNLIKELY( err ) ) {
765 0 : FD_TXN_PREPARE_ERR_OVERWRITE( vm->instr_ctx->txn_ctx );
766 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_BAD_SEEDS );
767 0 : return FD_VM_SYSCALL_ERR_BAD_SEEDS;
768 0 : }
769 0 : }
770 0 : }
771 :
772 : /* Create the instruction to execute (in the input format the FD runtime expects) from
773 : the translated CPI ABI inputs. */
774 0 : fd_pubkey_t cpi_instr_acct_keys[ FD_INSTR_ACCT_MAX ];
775 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++ ];
776 :
777 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 );
778 0 : if( FD_UNLIKELY( err ) ) {
779 0 : return err;
780 0 : }
781 :
782 : /* Authorized program check *************************************************/
783 :
784 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 ) ) ) ) {
785 : /* https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/programs/bpf_loader/src/syscalls/cpi.rs#L1054 */
786 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_PROGRAM_NOT_SUPPORTED );
787 0 : return FD_VM_SYSCALL_ERR_PROGRAM_NOT_SUPPORTED;
788 0 : }
789 :
790 : /* Prepare the instruction for execution in the runtime. This is required by the runtime
791 : before we can pass an instruction to the executor. */
792 0 : fd_instruction_account_t instruction_accounts[256];
793 0 : ulong instruction_accounts_cnt;
794 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 );
795 : /* Errors are propagated in the function itself. */
796 0 : if( FD_UNLIKELY( err ) ) {
797 0 : return err;
798 0 : }
799 :
800 : /* Translate account infos ******************************************/
801 :
802 : /* With stricter_abi_and_runtime_constraints, verify that the account_infos array
803 : is not inside the input region. This prevents programs from passing pointers to
804 : the serialized account data region as account_infos, which would allow them to
805 : bypass pointer validation checks.
806 : https://github.com/anza-xyz/agave/blob/v3.0.1/syscalls/src/cpi.rs#L735-L744 */
807 0 : ulong acc_info_total_sz = fd_ulong_sat_mul( acct_info_cnt, VM_SYSCALL_CPI_ACC_INFO_SIZE );
808 0 : if( vm->stricter_abi_and_runtime_constraints ) {
809 0 : if( FD_UNLIKELY( fd_ulong_sat_add( acct_infos_va, acc_info_total_sz ) >= FD_VM_MEM_MAP_INPUT_REGION_START ) ) {
810 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_INVALID_POINTER );
811 0 : return FD_VM_SYSCALL_ERR_INVALID_POINTER;
812 0 : }
813 0 : }
814 :
815 : /* This is the equivalent of translate_slice in translate_account_infos:
816 : https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/programs/bpf_loader/src/syscalls/cpi.rs#L816 */
817 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 );
818 :
819 : /* Right after translating, Agave checks the number of account infos:
820 : https://github.com/anza-xyz/agave/blob/v3.0.1/syscalls/src/cpi.rs#L752 */
821 0 : if( FD_UNLIKELY( acct_info_cnt > get_cpi_max_account_infos( vm->instr_ctx->txn_ctx ) ) ) {
822 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_MAX_INSTRUCTION_ACCOUNT_INFOS_EXCEEDED );
823 0 : return FD_VM_SYSCALL_ERR_MAX_INSTRUCTION_ACCOUNT_INFOS_EXCEEDED;
824 0 : }
825 :
826 0 : fd_pubkey_t const * acct_info_keys[ FD_CPI_MAX_ACCOUNT_INFOS ];
827 0 : for( ulong acct_idx = 0UL; acct_idx < acct_info_cnt; acct_idx++ ) {
828 : /* Translate each pubkey address specified in account_infos.
829 : Failed translation should lead to an access violation and
830 : implies that obviously bad account_info has been supplied.
831 : https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L833-L841 */
832 0 : acct_info_keys[ acct_idx ] = FD_VM_MEM_HADDR_LD( vm, acc_infos[ acct_idx ].pubkey_addr, alignof(uchar), sizeof(fd_pubkey_t) );
833 0 : }
834 :
835 : /* translate_and_update_accounts ************************************************************
836 : Update the callee accounts with any changes made by the caller prior to this CPI execution
837 :
838 : https://github.com/anza-xyz/agave/blob/v3.0.1/syscalls/src/cpi.rs#L767-L892 */
839 0 : fd_vm_cpi_caller_account_t caller_accounts[ 256 ];
840 0 : ushort callee_account_keys[256];
841 0 : ushort caller_accounts_to_update[256];
842 0 : ulong caller_accounts_to_update_len = 0;
843 0 : err = VM_SYSCALL_CPI_TRANSLATE_AND_UPDATE_ACCOUNTS_FUNC(
844 0 : vm,
845 0 : instruction_accounts,
846 0 : instruction_accounts_cnt,
847 0 : acct_infos_va,
848 0 : acct_info_keys,
849 0 : acc_infos,
850 0 : acct_info_cnt,
851 0 : callee_account_keys,
852 0 : caller_accounts_to_update,
853 0 : caller_accounts,
854 0 : &caller_accounts_to_update_len
855 0 : );
856 : /* errors are propagated in the function itself. */
857 0 : if( FD_UNLIKELY( err ) ) return err;
858 :
859 : /* Set the transaction compute meter to be the same as the VM's compute meter,
860 : so that the callee cannot use compute units that the caller has already used. */
861 0 : vm->instr_ctx->txn_ctx->compute_budget_details.compute_meter = vm->cu;
862 :
863 : /* Execute the CPI instruction in the runtime */
864 0 : int err_exec = fd_execute_instr( vm->instr_ctx->txn_ctx, instruction_to_execute );
865 0 : ulong instr_exec_res = (ulong)err_exec;
866 :
867 : /* Set the CU meter to the instruction context's transaction context's compute meter,
868 : so that the caller can't use compute units that the callee has already used. */
869 0 : vm->cu = vm->instr_ctx->txn_ctx->compute_budget_details.compute_meter;
870 :
871 0 : *_ret = instr_exec_res;
872 :
873 : /* Errors are propagated in fd_execute_instr. */
874 0 : if( FD_UNLIKELY( err_exec ) ) return err_exec;
875 :
876 : /* Update the caller accounts with any changes made by the callee during CPI execution */
877 0 : for( ulong i=0UL; i<caller_accounts_to_update_len; i++ ) {
878 : /* https://github.com/firedancer-io/solana/blob/508f325e19c0fd8e16683ea047d7c1a85f127e74/programs/bpf_loader/src/syscalls/cpi.rs#L939-L943 */
879 : /* We only want to update the writable accounts, because the non-writable
880 : caller accounts can't be changed during a CPI execution. */
881 0 : if( fd_instr_acc_is_writable_idx( vm->instr_ctx->instr, callee_account_keys[i] ) ) {
882 0 : ushort idx_in_txn = vm->instr_ctx->instr->accounts[ callee_account_keys[i] ].index_in_transaction;
883 0 : fd_pubkey_t const * callee = &vm->instr_ctx->txn_ctx->account_keys[ idx_in_txn ];
884 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);
885 0 : if( FD_UNLIKELY( err ) ) {
886 0 : return err;
887 0 : }
888 0 : }
889 0 : }
890 :
891 : /* With stricter_abi_and_runtime_constraints, update the caller's memory regions
892 : to reflect any changes the callee made to account data. This ensures the caller's
893 : view of account regions (tracked in acc_region_metas) remains consistent.
894 : https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1047-L1061 */
895 0 : if( vm->stricter_abi_and_runtime_constraints ) {
896 0 : for( ulong i=0UL; i<caller_accounts_to_update_len; i++ ) {
897 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1033-L1034 */
898 0 : fd_guarded_borrowed_account_t borrowed_callee_acc = {0};
899 0 : ushort idx_in_txn = vm->instr_ctx->instr->accounts[ callee_account_keys[i] ].index_in_transaction;
900 0 : fd_pubkey_t const * callee = &vm->instr_ctx->txn_ctx->account_keys[ idx_in_txn ];
901 0 : err = fd_exec_instr_ctx_try_borrow_instr_account_with_key( vm->instr_ctx, callee, &borrowed_callee_acc );
902 0 : if( FD_UNLIKELY( err && ( err != FD_ACC_MGR_ERR_UNKNOWN_ACCOUNT ) ) ) {
903 0 : return 1;
904 0 : }
905 :
906 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1052-L1058 */
907 0 : err = fd_vm_cpi_update_caller_account_region( vm, (ulong)callee_account_keys[i], caller_accounts + i, &borrowed_callee_acc );
908 0 : if( FD_UNLIKELY( err ) ) {
909 0 : return err;
910 0 : }
911 0 : }
912 0 : }
913 :
914 0 : return FD_VM_SUCCESS;
915 0 : }
916 :
917 : #undef VM_SYSCALL_CPI_UPDATE_CALLER_ACC_FUNC
918 : #undef VM_SYSCALL_CPI_FROM_ACC_INFO_FUNC
919 : #undef VM_SYSCALL_CPI_TRANSLATE_AND_UPDATE_ACCOUNTS_FUNC
920 : #undef VM_SYSCALL_CPI_INSTRUCTION_TO_INSTR_FUNC
921 : #undef VM_SYSCALL_CPI_FUNC
|