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