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/v4.0.0-beta.3/program-runtime/src/cpi.rs#L126-L144
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 11274 : if( FD_UNLIKELY( vm_addr!=expected_vm_addr )) { \
27 144 : fd_log_collector_printf_dangerous_max_127( vm->instr_ctx, \
28 144 : "Invalid account info pointer `%s': 0x%lx != 0x%lx", field_name, vm_addr, expected_vm_addr ); \
29 144 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_INVALID_POINTER ); \
30 144 : return FD_VM_SYSCALL_ERR_INVALID_POINTER; \
31 144 : }
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 3219 : #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 3219 : fd_pubkey_t out_instr_acct_keys[ FD_TXN_INSTR_ACCT_MAX ] ) {
63 :
64 3219 : out_instr->program_id = UCHAR_MAX;
65 3219 : out_instr->stack_height = (uchar)( vm->instr_ctx->runtime->instr.stack_sz+1 );
66 3219 : out_instr->data_sz = (ushort)VM_SYSCALL_CPI_INSTR_DATA_LEN( cpi_instr );
67 3219 : out_instr->acct_cnt = (ushort)VM_SYSCALL_CPI_INSTR_ACCS_LEN( cpi_instr );
68 3219 : 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 3219 : ulong program_id_idx = fd_runtime_find_index_of_account( vm->instr_ctx->txn_out, program_id );
72 3219 : if( FD_LIKELY( program_id_idx!=ULONG_MAX ) ) out_instr->program_id = (uchar)program_id_idx;
73 :
74 3219 : uchar acc_idx_seen[ FD_TXN_ACCT_ADDR_MAX ] = {0};
75 :
76 7599 : for( ushort i=0; i<VM_SYSCALL_CPI_INSTR_ACCS_LEN( cpi_instr ); i++ ) {
77 4380 : VM_SYSCALL_CPI_ACC_META_T const * cpi_acct_meta = &cpi_acct_metas[i];
78 6381 : fd_pubkey_t const * pubkey = fd_type_pun_const( VM_SYSCALL_CPI_ACC_META_PUBKEY( vm, cpi_acct_meta ) );
79 6381 : 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 6381 : out_instr->accounts[i] = fd_instruction_account_init( USHORT_MAX,
86 6381 : USHORT_MAX,
87 6381 : USHORT_MAX,
88 6381 : VM_SYSCALL_CPI_ACC_META_IS_WRITABLE( cpi_acct_meta ),
89 6381 : 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 6381 : ulong idx_in_txn = fd_runtime_find_index_of_account( vm->instr_ctx->txn_out, pubkey );
94 6381 : ulong idx_in_caller = fd_exec_instr_ctx_find_idx_of_instr_account( vm->instr_ctx, pubkey );
95 :
96 6381 : fd_instr_info_setup_instr_account( out_instr,
97 6381 : acc_idx_seen,
98 6381 : idx_in_txn!=ULONG_MAX ? (ushort)idx_in_txn : USHORT_MAX,
99 6381 : idx_in_caller!=ULONG_MAX ? (ushort)idx_in_caller : USHORT_MAX,
100 6381 : i,
101 6381 : VM_SYSCALL_CPI_ACC_META_IS_WRITABLE( cpi_acct_meta ),
102 6381 : VM_SYSCALL_CPI_ACC_META_IS_SIGNER( cpi_acct_meta ) );
103 :
104 6381 : }
105 :
106 3219 : return FD_VM_SUCCESS;
107 3219 : }
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/v4.0.0-beta.7/program-runtime/src/cpi.rs#L1211-L1273
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 : Parameters:
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 3726 : #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 : fd_borrowed_account_t * callee_acc,
134 3726 : uchar * out_must_update_caller ) {
135 3726 : int err;
136 3726 : *out_must_update_caller = 0;
137 :
138 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L1222-L1224 */
139 3726 : if( fd_borrowed_account_get_lamports( callee_acc )!=*(caller_account->lamports) ) {
140 303 : err = fd_borrowed_account_set_lamports( callee_acc, *(caller_account->lamports) );
141 303 : if( FD_UNLIKELY( err ) ) {
142 0 : FD_VM_ERR_FOR_LOG_INSTR( vm, err );
143 0 : return -1;
144 0 : }
145 303 : }
146 :
147 : /* With virtual_address_space_adjustments 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/v4.0.0-beta.3/program-runtime/src/cpi.rs#L1226-L1255 */
153 3726 : if( vm->virtual_address_space_adjustments ) {
154 1791 : ulong prev_len = fd_borrowed_account_get_data_len( callee_acc );
155 1791 : ulong post_len = *caller_account->ref_to_len_in_vm;
156 :
157 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L1229-L1251 */
158 1791 : if( FD_UNLIKELY( prev_len!=post_len ) ) {
159 : /* If the account has been shrunk, we're going to zero the unused
160 : memory that was previously used. */
161 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L1230-L1247 */
162 0 : if( FD_UNLIKELY( !vm->direct_mapping && ( post_len < prev_len ) ) ) {
163 0 : fd_memset( caller_account->serialized_data + post_len, 0, prev_len - post_len );
164 0 : }
165 :
166 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L1248 */
167 0 : err = fd_borrowed_account_set_data_length( callee_acc, post_len );
168 0 : if( FD_UNLIKELY( err ) ) {
169 0 : FD_VM_ERR_FOR_LOG_INSTR( vm, err );
170 0 : return -1;
171 0 : }
172 : /* Pointer to data may have changed, caller must be updated.
173 : https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L1248-L1250 */
174 0 : *out_must_update_caller = 1;
175 0 : }
176 :
177 : /* Without direct mapping, we need to copy the account data from the VM's
178 : serialized buffer back to the borrowed account. With direct mapping,
179 : data is modified in-place so no copy is needed.
180 : https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L1252-L1254 */
181 1791 : int err;
182 1791 : if( !vm->direct_mapping && fd_borrowed_account_can_data_be_changed( callee_acc, &err ) ) {
183 828 : err = fd_borrowed_account_set_data_from_slice( callee_acc, caller_account->serialized_data, caller_account->serialized_data_len );
184 828 : if( FD_UNLIKELY( err ) ) {
185 0 : FD_VM_ERR_FOR_LOG_INSTR( vm, err );
186 0 : return -1;
187 0 : }
188 828 : }
189 1935 : } else {
190 : /* Direct mapping is not enabled, so we need to copy the account data
191 : from the VM's serialized buffer back to the borrowed account.
192 :
193 : https://github.com/anza-xyz/agave/blob/v4.2.0-beta.0/program-runtime/src/cpi.rs#L1155-L1162 */
194 1935 : int err;
195 1935 : if( fd_borrowed_account_can_data_be_resized( callee_acc, caller_account->serialized_data_len, &err ) ) {
196 : /* https://github.com/anza-xyz/agave/blob/v4.2.0-beta.0/program-runtime/src/cpi.rs#L1157 */
197 1815 : err = fd_borrowed_account_set_data_from_slice( callee_acc, caller_account->serialized_data, caller_account->serialized_data_len );
198 1815 : if( FD_UNLIKELY( err ) ) {
199 0 : FD_VM_ERR_FOR_LOG_INSTR( vm, err );
200 0 : return -1;
201 0 : }
202 1815 : } else if( FD_UNLIKELY( caller_account->serialized_data_len!=fd_borrowed_account_get_data_len( callee_acc ) ||
203 120 : (caller_account->serialized_data_len &&
204 120 : memcmp( fd_borrowed_account_get_data( callee_acc ), caller_account->serialized_data, caller_account->serialized_data_len )) ) ) {
205 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L1259-L1261 */
206 0 : FD_VM_ERR_FOR_LOG_INSTR( vm, err );
207 0 : return -1;
208 0 : }
209 1935 : }
210 :
211 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L1266-L1271 */
212 3726 : if( FD_UNLIKELY( memcmp( fd_borrowed_account_get_owner( callee_acc ), caller_account->owner, sizeof(fd_pubkey_t) ) ) ) {
213 309 : err = fd_borrowed_account_set_owner( callee_acc, caller_account->owner );
214 309 : if( FD_UNLIKELY( err ) ) {
215 0 : FD_VM_ERR_FOR_LOG_INSTR( vm, err );
216 0 : return -1;
217 0 : }
218 : /* Caller gave ownership and thus write access away, so caller must be updated.
219 : https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L1268-L1270 */
220 309 : *out_must_update_caller = 1;
221 309 : }
222 :
223 3726 : return FD_VM_SUCCESS;
224 3726 : }
225 :
226 : /*
227 : fd_vm_syscall_cpi_translate_and_update_accounts_ mirrors the behaviour of
228 : solana_program_runtime::cpi::SyscallInvokeSigned::translate_accounts_common:
229 : https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L1049-L1193
230 :
231 : It translates the caller accounts to the host address space, and then calls
232 : fd_vm_syscall_cpi_update_callee_acc to update the callee borrowed account with any changes
233 : the caller has made to the account during execution before this CPI call.
234 :
235 : Parameters:
236 : - vm: pointer to the virtual machine handle
237 : - instruction_accounts: array of instruction accounts
238 : - instruction_accounts_cnt: length of the instruction_accounts array
239 : - account_infos: array of account infos
240 : - account_infos_length: length of the account_infos array
241 :
242 : Populates:
243 : - translated_accounts: the translated account entries
244 : - out_len: number of translated account entries
245 : */
246 3069 : #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)
247 : static int
248 : VM_SYSCALL_CPI_TRANSLATE_AND_UPDATE_ACCOUNTS_FUNC(
249 : fd_vm_t * vm,
250 : fd_instruction_account_t const * instruction_accounts,
251 : ulong const instruction_accounts_cnt,
252 : ulong acct_infos_va,
253 : fd_pubkey_t const * * account_info_keys, /* same length as account_infos_length */
254 : VM_SYSCALL_CPI_ACC_INFO_T const * account_infos,
255 : ulong const account_infos_length,
256 : fd_vm_cpi_translated_account_t * translated_accounts,
257 3069 : ulong * out_len ) {
258 7035 : for( ulong i=0UL; i<instruction_accounts_cnt; i++ ) {
259 4230 : if( i!=instruction_accounts[i].index_in_callee ) {
260 : /* Skip duplicate accounts */
261 144 : continue;
262 144 : }
263 :
264 : /* `fd_vm_prepare_instruction()` will always set up a valid index for `index_in_caller`, so we can access the borrowed account directly.
265 : A borrowed account will always have non-NULL meta (if the account doesn't exist, `fd_executor_setup_accounts_for_txn()`
266 : will set its meta up) */
267 :
268 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L1102 */
269 4086 : fd_guarded_borrowed_account_t callee_acct = {0};
270 4086 : FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( vm->instr_ctx, instruction_accounts[i].index_in_caller, &callee_acct );
271 :
272 4080 : fd_pubkey_t const * account_key = (fd_pubkey_t*)callee_acct.acc->pubkey;
273 :
274 : /* If the account is known and executable, we only need to consume the compute units.
275 : Executable accounts can't be modified, so we don't need to update the callee account. */
276 4080 : if( fd_borrowed_account_is_executable( &callee_acct ) ) {
277 : // FIXME: should this be FD_VM_CU_MEM_UPDATE? Changing this changes the CU behaviour from main (because of the base cost)
278 96 : FD_VM_CU_UPDATE( vm, callee_acct.acc->data_len / FD_VM_CPI_BYTES_PER_UNIT );
279 96 : continue;
280 96 : }
281 :
282 : /* FIXME: we should not need to drop the account here to avoid a double borrow.
283 : Instead, we should borrow the account before entering this function. */
284 3984 : fd_borrowed_account_drop( &callee_acct );
285 :
286 : /* Find the indices of the account in the caller and callee instructions */
287 3984 : uint found = 0;
288 10839 : for( ushort j=0; j<account_infos_length && !found; j++ ) {
289 7065 : fd_pubkey_t const * acct_addr = account_info_keys[ j ];
290 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L1117
291 : */
292 7065 : if( memcmp( account_key->uc, acct_addr->uc, sizeof(fd_pubkey_t) ) != 0 ) {
293 3129 : continue;
294 3129 : }
295 :
296 3936 : fd_vm_cpi_translated_account_t * translated_account = translated_accounts + *out_len;
297 3936 : fd_vm_cpi_caller_account_t * caller_account = &translated_account->caller_account;
298 3936 : ushort index_in_caller = instruction_accounts[i].index_in_caller;
299 3936 : translated_account->index_in_caller = index_in_caller;
300 3936 : translated_account->update_caller_account_info = (uchar)!!instruction_accounts[i].is_writable;
301 3936 : found = 1;
302 :
303 : /* Logically this check isn't ever going to fail due to how the
304 : account_info_keys array is set up. We replicate the check for
305 : clarity and also to guard against accidental violation of the
306 : assumed invariant in the future.
307 : https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L1131-L1134
308 : */
309 3936 : if( FD_UNLIKELY( j >= account_infos_length ) ) {
310 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_INVALID_LENGTH );
311 0 : return FD_VM_SYSCALL_ERR_INVALID_LENGTH;
312 0 : }
313 :
314 : /* The following implements the checks in from_account_info which
315 : is invoked as do_translate() in translate_and_update_accounts()
316 : https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L1135-L1146
317 : */
318 : ////// BEGIN from_account_info
319 :
320 3936 : fd_vm_acc_region_meta_t * acc_region_meta = &vm->acc_region_metas[index_in_caller];
321 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L321-L334 */
322 3936 : if( FD_LIKELY( vm->syscall_parameter_address_restrictions ) ) {
323 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L322-L327 */
324 2859 : ulong expected_pubkey_vaddr = acc_region_meta->vm_key_addr;
325 : /* Max msg_sz: 40 + 18 + 18 = 76 < 127 */
326 2859 : VM_SYSCALL_CPI_CHECK_ACCOUNT_INFO_POINTER_FIELD_MAX_54(vm, account_infos[j].pubkey_addr, expected_pubkey_vaddr, "key");
327 :
328 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L328-L333 */
329 2859 : ulong expected_owner_vaddr = acc_region_meta->vm_owner_addr;
330 : /* Max msg_sz: 42 + 18 + 18 = 78 < 127 */
331 2859 : VM_SYSCALL_CPI_CHECK_ACCOUNT_INFO_POINTER_FIELD_MAX_54(vm, account_infos[j].owner_addr, expected_owner_vaddr, "owner");
332 2823 : }
333 :
334 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L336-L358 */
335 9939 : VM_SYSCALL_CPI_ACC_INFO_LAMPORTS_VADDR( vm, (account_infos + j), lamports_vaddr );
336 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L345-L356 */
337 9939 : if( FD_LIKELY( vm->syscall_parameter_address_restrictions ) ) {
338 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L346-L348
339 : Check that the account's lamports Rc<RefCell<&mut u64>> is not
340 : stored in the account region. Because a refcell is only present if
341 : the Rust SDK is used, we only need to check this for the Rust ABI. */
342 : #ifdef VM_SYSCALL_CPI_ACC_INFO_LAMPORTS_RC_REFCELL_VADDR
343 1431 : VM_SYSCALL_CPI_ACC_INFO_LAMPORTS_RC_REFCELL_VADDR( vm, (account_infos + j), lamports_rc_vaddr )
344 1431 : if ( FD_UNLIKELY( lamports_rc_vaddr >= FD_VM_MEM_MAP_INPUT_REGION_START ) ) {
345 18 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_INVALID_POINTER );
346 18 : return FD_VM_SYSCALL_ERR_INVALID_POINTER;
347 18 : }
348 1413 : #endif
349 :
350 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L350-L355 */
351 1413 : ulong expected_lamports_vaddr = acc_region_meta->vm_lamports_addr;
352 : /* Max msg_sz: 45 + 18 + 18 = 81 < 127 */
353 2805 : VM_SYSCALL_CPI_CHECK_ACCOUNT_INFO_POINTER_FIELD_MAX_54(vm, lamports_vaddr, expected_lamports_vaddr, "lamports");
354 2769 : }
355 :
356 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L357
357 : */
358 13611 : VM_SYSCALL_CPI_ACC_INFO_LAMPORTS( vm, (account_infos + j), lamports_haddr );
359 13611 : caller_account->lamports = lamports_haddr;
360 :
361 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L360-L364
362 : */
363 13611 : caller_account->owner = FD_VM_MEM_HADDR_ST( vm, (account_infos + j)->owner_addr, alignof(uchar), sizeof(fd_pubkey_t) );
364 :
365 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L367-L378
366 : */
367 7800 : VM_SYSCALL_CPI_ACC_INFO_DATA_VADDR( vm, (account_infos + j), data_vaddr );
368 :
369 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L379-L386 */
370 7800 : if( vm->syscall_parameter_address_restrictions ) {
371 2751 : VM_SYSCALL_CPI_CHECK_ACCOUNT_INFO_POINTER_FIELD_MAX_54(
372 2751 : vm, data_vaddr, acc_region_meta->vm_data_addr, "data");
373 2679 : } else {
374 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L388-L392 */
375 1065 : VM_SYSCALL_CPI_SET_ACC_INFO_DATA_GET_LEN( vm, (account_infos + j), data_vaddr );
376 1065 : FD_VM_CU_UPDATE( vm, data_vaddr_len / FD_VM_CPI_BYTES_PER_UNIT );
377 1059 : }
378 :
379 : #ifdef VM_SYSCALL_CPI_ACC_INFO_DATA_LEN_VADDR
380 : /* Rust ABI
381 : https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L395-L404 */
382 1908 : VM_SYSCALL_CPI_ACC_INFO_DATA_LEN_VADDR( vm, (account_infos + j), data_len_vaddr );
383 1908 : (void)acct_infos_va;
384 : #else
385 : /* C ABI
386 : https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L508-L514 */
387 1830 : ulong data_len_vaddr = vm_syscall_cpi_data_len_vaddr_c(
388 1830 : fd_ulong_sat_add( acct_infos_va, fd_ulong_sat_mul( j, VM_SYSCALL_CPI_ACC_INFO_SIZE ) ),
389 1830 : (ulong)&((account_infos + j)->data_sz),
390 1830 : (ulong)(account_infos + j)
391 1830 : );
392 1830 : #endif
393 :
394 : /* Rust ABI: https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L397-L404
395 : C ABI: https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L515-L522 */
396 3738 : if( FD_UNLIKELY( vm->syscall_parameter_address_restrictions && data_len_vaddr >= FD_VM_MEM_MAP_INPUT_REGION_START ) ) {
397 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_INVALID_POINTER );
398 0 : return FD_VM_SYSCALL_ERR_INVALID_POINTER;
399 0 : }
400 :
401 : /* Rust ABI: https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L411
402 : C ABI: https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L545 */
403 3738 : caller_account->vm_data_vaddr = data_vaddr;
404 :
405 : /* Rust ABI: https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L405-L406
406 : C ABI: https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L523-L524 */
407 3738 : ulong * data_len = FD_VM_MEM_HADDR_ST( vm, data_len_vaddr, 1UL, sizeof(ulong) );
408 3738 : caller_account->ref_to_len_in_vm = data_len;
409 :
410 : /* Rust ABI: https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L408-L421
411 : C ABI: https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L525-L538
412 :
413 : Both ABIs call CallerAccount::get_serialized_data:
414 : https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L250-L299 */
415 :
416 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L262-L272 */
417 3738 : if( vm->syscall_parameter_address_restrictions ) {
418 2679 : ulong address_space_reserved_for_account;
419 2679 : if( vm->is_deprecated ) {
420 1338 : address_space_reserved_for_account = acc_region_meta->original_data_len;
421 1341 : } else {
422 1341 : address_space_reserved_for_account = fd_ulong_sat_add( acc_region_meta->original_data_len, MAX_PERMITTED_DATA_INCREASE );
423 1341 : }
424 2679 : if( FD_UNLIKELY( *data_len > address_space_reserved_for_account ) ) {
425 0 : FD_VM_ERR_FOR_LOG_INSTR( vm, FD_EXECUTOR_INSTR_ERR_INVALID_REALLOC );
426 0 : return -1;
427 0 : }
428 2679 : }
429 :
430 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L273-L298
431 :
432 : With both virtual_address_space_adjustments and direct_mapping,
433 : account data is modified in-place so we don't track the
434 : serialized_data pointer.
435 :
436 : With virtual_address_space_adjustments only (no direct_mapping), data was copied into the input
437 : region buffer. We don't apply the extra memory translation checks, as
438 : we have checked the data pointer is valid above. So instead we add
439 : the vaddr to the start of the input region address space - copying
440 : this logic from Agave.
441 :
442 : In legacy mode, we translate the data pointer directly, as it just
443 : maps to a location in the single input region. */
444 3738 : if( vm->virtual_address_space_adjustments && vm->direct_mapping ) {
445 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L273-L275 */
446 903 : caller_account->serialized_data = NULL;
447 903 : caller_account->serialized_data_len = 0UL;
448 2835 : } else if( vm->virtual_address_space_adjustments ) {
449 : /* Skip translation checks here, following the Agave logic:
450 : https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L275-L291 */
451 1776 : uchar * serialization_ptr = (uchar *)FD_VM_MEM_SLICE_HADDR_ST( vm, FD_VM_MEM_MAP_INPUT_REGION_START, alignof(uchar), 1UL );
452 1776 : caller_account->serialized_data = serialization_ptr + fd_ulong_sat_sub( data_vaddr, FD_VM_MEM_MAP_INPUT_REGION_START );
453 1776 : caller_account->serialized_data_len = *data_len;
454 1947 : } else {
455 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L291-L298 */
456 6915 : VM_SYSCALL_CPI_ACC_INFO_DATA( vm, (account_infos + j), data_haddr );
457 6915 : (void)data_haddr_vm_addr;
458 6915 : caller_account->serialized_data = data_haddr;
459 6915 : caller_account->serialized_data_len = data_haddr_len;
460 6915 : }
461 :
462 : /* Rust ABI: https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L428
463 : C ABI: https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L428 */
464 3738 : caller_account->orig_data_len = acc_region_meta->original_data_len;
465 :
466 : ////// END from_account_info
467 :
468 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L1148-L1156 */
469 3726 : if( vm->syscall_parameter_address_restrictions ) {
470 2679 : FD_VM_CU_UPDATE( vm, *data_len / FD_VM_CPI_BYTES_PER_UNIT );
471 2679 : }
472 :
473 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L1157-L1181 */
474 3726 : uchar update_caller = 0;
475 3726 : if( vm->syscall_parameter_address_restrictions ) {
476 2679 : update_caller = 1;
477 2679 : } else {
478 1047 : fd_guarded_borrowed_account_t callee_acc = {0};
479 1047 : FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( vm->instr_ctx, index_in_caller, &callee_acc );
480 1047 : int err = VM_SYCALL_CPI_UPDATE_CALLEE_ACC_FUNC( vm, caller_account, &callee_acc, &update_caller );
481 1047 : if( FD_UNLIKELY( err ) ) {
482 0 : return err;
483 0 : }
484 1047 : }
485 3726 : translated_account->update_caller_account_region =
486 3726 : (uchar)( translated_account->update_caller_account_info || update_caller );
487 3726 : (*out_len)++;
488 3726 : }
489 :
490 3774 : if( !found ) {
491 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.3/program-runtime/src/cpi.rs#L1183-L1188 */
492 48 : FD_BASE58_ENCODE_32_BYTES( account_key->uc, id_b58 );
493 48 : fd_log_collector_msg_many( vm->instr_ctx, 2, "Instruction references an unknown account ", 42UL, id_b58, id_b58_len );
494 48 : FD_VM_ERR_FOR_LOG_INSTR( vm, FD_EXECUTOR_INSTR_ERR_MISSING_ACC );
495 48 : return FD_EXECUTOR_INSTR_ERR_MISSING_ACC;
496 48 : }
497 3774 : }
498 :
499 2805 : return FD_VM_SUCCESS;
500 3069 : }
501 :
502 : /* fd_vm_cpi_update_caller_acc_{rust/c} mirrors the behaviour of
503 : solana_bpf_loader_program::syscalls::cpi::update_caller_account:
504 : https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1171-L1268
505 :
506 : This method should be called after a CPI instruction execution has
507 : returned. It updates the given caller account info with any changes the callee
508 : has made to this account during execution, so that those changes are
509 : reflected in the rest of the caller's execution.
510 :
511 : Those changes will be in the instructions borrowed accounts cache.
512 :
513 : Parameters:
514 : - vm: handle to the vm
515 : - caller_acc_info: caller account info object, which should be updated
516 : - borrowed_callee_acc: already-borrowed callee account
517 : */
518 2790 : #define VM_SYSCALL_CPI_UPDATE_CALLER_ACC_FUNC FD_EXPAND_THEN_CONCAT2(fd_vm_cpi_update_caller_acc_, VM_SYSCALL_CPI_ABI)
519 : static int
520 : VM_SYSCALL_CPI_UPDATE_CALLER_ACC_FUNC( fd_vm_t * vm,
521 : fd_vm_cpi_caller_account_t * caller_account,
522 2790 : fd_borrowed_account_t * borrowed_callee_acc ) {
523 :
524 : /* Update the caller account lamports with the value from the callee
525 : https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1191 */
526 2790 : *(caller_account->lamports) = borrowed_callee_acc->acc->lamports;
527 :
528 : /* Update the caller account owner with the value from the callee
529 : https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1192 */
530 2790 : fd_pubkey_t const * updated_owner = (fd_pubkey_t const *)borrowed_callee_acc->acc->owner;
531 2790 : if( updated_owner ) *caller_account->owner = *updated_owner;
532 0 : else fd_memset( caller_account->owner, 0, sizeof(fd_pubkey_t) );
533 :
534 : /* Update the caller account data with the value from the callee
535 : https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1194-L1195 */
536 2790 : ulong prev_len = *caller_account->ref_to_len_in_vm;
537 2790 : ulong post_len = borrowed_callee_acc->acc->data_len;
538 :
539 : /* Calculate the address space reserved for the account. With syscall_parameter_address_restrictions
540 : and deprecated loader, the reserved space equals original length (no realloc space).
541 : Otherwise, we add MAX_PERMITTED_DATA_INCREASE for reallocation.
542 : https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1197-L1204 */
543 2790 : ulong address_space_reserved_for_account;
544 2790 : if( vm->syscall_parameter_address_restrictions && vm->is_deprecated ) {
545 996 : address_space_reserved_for_account = caller_account->orig_data_len;
546 1794 : } else {
547 1794 : address_space_reserved_for_account = fd_ulong_sat_add( caller_account->orig_data_len, MAX_PERMITTED_DATA_INCREASE );
548 1794 : }
549 :
550 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1206-L1216 */
551 2790 : if( post_len > address_space_reserved_for_account &&
552 2790 : ( vm->syscall_parameter_address_restrictions || prev_len != post_len ) ) {
553 162 : ulong max_increase = fd_ulong_sat_sub( address_space_reserved_for_account, caller_account->orig_data_len );
554 162 : fd_log_collector_printf_dangerous_max_127( vm->instr_ctx, "Account data size realloc limited to %lu in inner instructions", max_increase );
555 162 : FD_VM_ERR_FOR_LOG_INSTR( vm, FD_EXECUTOR_INSTR_ERR_INVALID_REALLOC );
556 162 : return FD_EXECUTOR_INSTR_ERR_INVALID_REALLOC;
557 162 : }
558 :
559 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1218-L1252 */
560 2628 : if( prev_len != post_len ) {
561 :
562 : /* Without direct mapping, we need to adjust the serialized data buffer
563 : when the length changes.
564 :
565 : With direct mapping, data is mapped in-place so no buffer manipulation
566 : is needed.
567 :
568 : https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1219-L1239 */
569 510 : if( !( vm->virtual_address_space_adjustments && vm->direct_mapping ) ) {
570 :
571 : /* If the account has shrunk, zero out memory that was previously used
572 : https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1222-L1230 */
573 408 : if( post_len < prev_len ) {
574 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1227-L1228 */
575 156 : if( caller_account->serialized_data_len < post_len ) {
576 0 : FD_VM_ERR_FOR_LOG_INSTR( vm, FD_EXECUTOR_INSTR_ERR_ACC_DATA_TOO_SMALL );
577 0 : return FD_EXECUTOR_INSTR_ERR_ACC_DATA_TOO_SMALL;
578 0 : }
579 :
580 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1225-L1229 */
581 156 : fd_memset( caller_account->serialized_data + post_len, 0, caller_account->serialized_data_len - post_len );
582 156 : }
583 :
584 : /* Set caller_account.serialized_data to post_len.
585 : https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1231-L1238 */
586 408 : if( vm->virtual_address_space_adjustments ) {
587 : /* Calculate the serialized data pointer from the input region base,
588 : as described above.
589 :
590 : https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L99-L115 */
591 204 : uchar * serialization_ptr = (uchar *)FD_VM_MEM_SLICE_HADDR_ST( vm, FD_VM_MEM_MAP_INPUT_REGION_START, alignof(uchar), 1UL );
592 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1234 */
593 204 : caller_account->serialized_data = serialization_ptr + fd_ulong_sat_sub( caller_account->vm_data_vaddr, FD_VM_MEM_MAP_INPUT_REGION_START );
594 204 : caller_account->serialized_data_len = post_len;
595 306 : } else {
596 : /* Translate the data pointer directly from the VM address, if
597 : virtual_address_space_adjustments (or direct mapping) is not
598 : enabled.
599 :
600 : https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L115-L122 */
601 612 : caller_account->serialized_data = (uchar *)FD_VM_MEM_SLICE_HADDR_ST( vm, caller_account->vm_data_vaddr, alignof(uchar), post_len );
602 612 : caller_account->serialized_data_len = post_len;
603 612 : }
604 408 : }
605 :
606 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1240-L1241 */
607 510 : *caller_account->ref_to_len_in_vm = post_len;
608 :
609 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1243-L1251 */
610 510 : ulong * caller_len = FD_VM_MEM_HADDR_ST( vm, fd_ulong_sat_sub(caller_account->vm_data_vaddr, sizeof(ulong)), alignof(ulong), sizeof(ulong) );
611 510 : *caller_len = post_len;
612 510 : }
613 :
614 : /* Without direct mapping, copy the updated account data from the callee's
615 : account back to the caller's serialized data buffer. With direct mapping,
616 : data was modified in-place so no copy is needed.
617 :
618 : https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1254-L1265 */
619 2628 : if( !(vm->virtual_address_space_adjustments && vm->direct_mapping) ) {
620 :
621 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1261-L1263 */
622 2007 : if( FD_UNLIKELY( caller_account->serialized_data_len!=post_len ) ) {
623 3 : FD_VM_ERR_FOR_LOG_INSTR( vm, FD_EXECUTOR_INSTR_ERR_ACC_DATA_TOO_SMALL );
624 3 : return FD_EXECUTOR_INSTR_ERR_ACC_DATA_TOO_SMALL;
625 3 : }
626 :
627 2004 : fd_memcpy( caller_account->serialized_data, borrowed_callee_acc->acc->data, post_len );
628 2004 : }
629 :
630 :
631 2625 : return FD_VM_SUCCESS;
632 2628 : }
633 :
634 : /* fd_vm_syscall_cpi_{rust/c} is the entrypoint for the sol_invoke_signed_{rust/c} syscalls.
635 :
636 : The bulk of the high-level logic mirrors Solana's cpi_common entrypoint function at
637 : https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L847-L977
638 : The only differences should be in the order of the error checks, which does not affect consensus.
639 :
640 : 100-foot flow:
641 : - Translate the CPI ABI structures to the FD runtime's instruction format
642 : - Update the callee accounts with any changes made by the caller prior to this CPI instruction
643 : - Dispatch the instruction to the FD runtime (actually making the CPI call)
644 : - Update the caller accounts with any changes made by the callee during CPI execution
645 :
646 : Parameters:
647 : - vm: pointer to the virtual machine handle
648 : - instruction_va: vm address of the instruction to execute, which will be in the language-specific ABI format.
649 : - acct_infos_va: vm address of the account infos, which will be in the language-specific ABI format.
650 : - acct_info_cnt: number of account infos
651 : - signers_seeds_va: vm address of the signers seeds
652 : - signers_seeds_cnt: number of signers seeds
653 : - _ret: pointer to the return value
654 : */
655 : #define VM_SYSCALL_CPI_ENTRYPOINT FD_EXPAND_THEN_CONCAT2(fd_vm_syscall_cpi_, VM_SYSCALL_CPI_ABI)
656 : int
657 : VM_SYSCALL_CPI_ENTRYPOINT( void * _vm,
658 : ulong instruction_va,
659 : ulong acct_infos_va,
660 : ulong acct_info_cnt,
661 : ulong signers_seeds_va,
662 : ulong signers_seeds_cnt,
663 3249 : ulong * _ret ) {
664 3249 : long const regime0 = fd_tickcount();
665 :
666 3249 : fd_vm_t * vm = (fd_vm_t *)_vm;
667 :
668 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L859-L864 */
669 3249 : FD_VM_CU_UPDATE( vm, get_cpi_invoke_unit_cost() );
670 :
671 : /* Translate instruction ********************************************/
672 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L878-L883
673 : Rust ABI: https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L575-L636
674 : C ABI: https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L709-L774 */
675 :
676 : /* Translating the CPI instruction
677 : Rust ABI: https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L581
678 : C ABI: https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L715 */
679 3249 : VM_SYSCALL_CPI_INSTR_T const * cpi_instruction =
680 9747 : FD_VM_MEM_HADDR_LD( vm, instruction_va, VM_SYSCALL_CPI_INSTR_ALIGN, VM_SYSCALL_CPI_INSTR_SIZE );
681 :
682 : /* This needs to be here for the C ABI
683 : https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L717
684 : */
685 9747 : fd_pubkey_t const * program_id = (fd_pubkey_t *)VM_SYSCALL_CPI_INSTR_PROGRAM_ID( vm, cpi_instruction );
686 :
687 : /* Translate CPI account metas
688 : Rust ABI: https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L582-L587
689 : C ABI: https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L718-L723 */
690 4689 : VM_SYSCALL_CPI_ACC_META_T const * cpi_account_metas =
691 6498 : FD_VM_MEM_SLICE_HADDR_LD( vm, VM_SYSCALL_CPI_INSTR_ACCS_ADDR( cpi_instruction ),
692 6498 : VM_SYSCALL_CPI_ACC_META_ALIGN,
693 6498 : fd_ulong_sat_mul( VM_SYSCALL_CPI_INSTR_ACCS_LEN( cpi_instruction ), VM_SYSCALL_CPI_ACC_META_SIZE ) );
694 :
695 : /* Translate instruction data
696 : Rust ABI: https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L588-L593
697 : C ABI: https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L724 */
698 :
699 6498 : uchar const * data = FD_VM_MEM_SLICE_HADDR_LD(
700 6498 : vm, VM_SYSCALL_CPI_INSTR_DATA_ADDR( cpi_instruction ),
701 6498 : FD_VM_ALIGN_RUST_U8,
702 6498 : VM_SYSCALL_CPI_INSTR_DATA_LEN( cpi_instruction ));
703 :
704 :
705 : /* Instruction checks
706 : Rust ABI: https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L595
707 : C ABI: https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L726 */
708 :
709 6498 : int err = fd_vm_syscall_cpi_check_instruction( VM_SYSCALL_CPI_INSTR_ACCS_LEN( cpi_instruction ), VM_SYSCALL_CPI_INSTR_DATA_LEN( cpi_instruction ) );
710 6498 : if( FD_UNLIKELY( err ) ) {
711 6 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, err );
712 6 : return err;
713 6 : }
714 :
715 : /* Agave consumes CU in translate_instruction
716 : Rust ABI: https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L597-L599
717 : C ABI: https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L728-L730 */
718 3243 : ulong total_cu_translation_cost = VM_SYSCALL_CPI_INSTR_DATA_LEN( cpi_instruction ) / FD_VM_CPI_BYTES_PER_UNIT;
719 :
720 : /* Rust ABI: https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L601-L613
721 : C ABI: https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L732-L745 */
722 : /* Agave bills the same regardless of ABI */
723 3243 : ulong account_meta_translation_cost =
724 3243 : fd_ulong_sat_mul(
725 3243 : VM_SYSCALL_CPI_INSTR_ACCS_LEN( cpi_instruction ),
726 3243 : FD_VM_RUST_ACCOUNT_META_SIZE ) /
727 3243 : FD_VM_CPI_BYTES_PER_UNIT;
728 3243 : total_cu_translation_cost = fd_ulong_sat_add( total_cu_translation_cost, account_meta_translation_cost );
729 3243 : FD_VM_CU_UPDATE( vm, total_cu_translation_cost );
730 :
731 : /* Rust ABI: https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L617-L629
732 : C ABI: https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L749-L767 */
733 7647 : for( ulong i=0UL; i<VM_SYSCALL_CPI_INSTR_ACCS_LEN( cpi_instruction ); i++ ) {
734 4404 : VM_SYSCALL_CPI_ACC_META_T const * cpi_acct_meta = &cpi_account_metas[i];
735 4404 : if( FD_UNLIKELY( cpi_acct_meta->is_signer > 1U || cpi_acct_meta->is_writable > 1U ) ) {
736 0 : FD_VM_ERR_FOR_LOG_INSTR( vm, FD_EXECUTOR_INSTR_ERR_INVALID_ARG );
737 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ARG;
738 0 : }
739 : /* Rust ABI: no-op
740 : C ABI: https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L760-L761
741 : */
742 6417 : (void)VM_SYSCALL_CPI_ACC_META_PUBKEY( vm, cpi_acct_meta );
743 6417 : }
744 :
745 : /* Derive PDA signers
746 : https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L887-L893
747 : Rust ABI: https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L665-L707
748 : C ABI: https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L803-L845
749 :
750 : Note that we don't need any ABI-specific logic here, because the two ABIs are actually identical for the seeds.*/
751 3243 : fd_pubkey_t signers[ FD_CPI_MAX_SIGNER_CNT ] = {0};
752 3243 : fd_pubkey_t * caller_program_id = &vm->instr_ctx->txn_out->accounts.keys[ vm->instr_ctx->instr->program_id ];
753 3243 : if( FD_LIKELY( signers_seeds_cnt > 0UL ) ) {
754 324 : 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 ) );
755 324 : if( FD_UNLIKELY( signers_seeds_cnt > FD_CPI_MAX_SIGNER_CNT ) ) {
756 6 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_TOO_MANY_SIGNERS );
757 6 : return FD_VM_SYSCALL_ERR_TOO_MANY_SIGNERS;
758 6 : }
759 :
760 300 : for( ulong i=0UL; i<signers_seeds_cnt; i++ ) {
761 :
762 : /* This function will precompute the memory translation required and do
763 : some preflight checks. */
764 156 : void const * signer_seed_haddrs[ FD_VM_PDA_SEEDS_MAX ];
765 156 : ulong signer_seed_lens [ FD_VM_PDA_SEEDS_MAX ];
766 :
767 156 : int err = fd_vm_translate_and_check_program_address_inputs( vm,
768 156 : signers_seeds[i].addr,
769 156 : signers_seeds[i].len,
770 156 : 0UL,
771 156 : signer_seed_haddrs,
772 156 : signer_seed_lens ,
773 156 : NULL,
774 156 : 0U );
775 156 : if( FD_UNLIKELY( err ) ) {
776 6 : return err;
777 6 : }
778 :
779 150 : err = fd_vm_derive_pda( vm, caller_program_id, signer_seed_haddrs, signer_seed_lens, signers_seeds[i].len, NULL, &signers[i] );
780 150 : if( FD_UNLIKELY( err ) ) {
781 6 : FD_TXN_PREPARE_ERR_OVERWRITE( vm->instr_ctx->txn_out );
782 6 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_BAD_SEEDS );
783 6 : return FD_VM_SYSCALL_ERR_BAD_SEEDS;
784 6 : }
785 150 : }
786 156 : }
787 :
788 : /* Authorized program check
789 : https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L894 */
790 3225 : 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 ) ) ) ) {
791 6 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_PROGRAM_NOT_SUPPORTED );
792 6 : return FD_VM_SYSCALL_ERR_PROGRAM_NOT_SUPPORTED;
793 6 : }
794 :
795 : /* Create the instruction to execute (in the input format the FD runtime expects) from
796 : the translated CPI ABI inputs.
797 : https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L895 */
798 3219 : fd_pubkey_t cpi_instr_acct_keys[ FD_TXN_INSTR_ACCT_MAX ];
799 3219 : fd_instr_info_t * instruction_to_execute = &vm->instr_ctx->runtime->instr.trace[ vm->instr_ctx->runtime->instr.trace_length++ ];
800 :
801 3219 : err = VM_SYSCALL_CPI_INSTRUCTION_TO_INSTR_FUNC( vm, cpi_instruction, cpi_account_metas, program_id, data, instruction_to_execute, cpi_instr_acct_keys );
802 3219 : if( FD_UNLIKELY( err ) ) {
803 0 : return err;
804 0 : }
805 :
806 : /* Prepare the instruction for execution in the runtime. This is required by the runtime
807 : before we can pass an instruction to the executor. */
808 3219 : fd_instruction_account_t instruction_accounts[ FD_TXN_INSTR_ACCT_MAX ];
809 3219 : ulong instruction_accounts_cnt;
810 3219 : 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 );
811 : /* Errors are propagated in the function itself. */
812 3219 : if( FD_UNLIKELY( err ) ) {
813 96 : return err;
814 96 : }
815 :
816 : /* Translate account infos
817 : https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L897-L903
818 : https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L987-L1047 */
819 :
820 : /* With syscall_parameter_address_restrictions, verify that the account_infos array
821 : is not inside the input region. This prevents programs from passing pointers to
822 : the serialized account data region as account_infos, which would allow them to
823 : bypass pointer validation checks.
824 : https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L1002-L1011 */
825 3123 : ulong acc_info_total_sz = fd_ulong_sat_mul( acct_info_cnt, VM_SYSCALL_CPI_ACC_INFO_SIZE );
826 3123 : if( vm->syscall_parameter_address_restrictions ) {
827 2247 : if( FD_UNLIKELY( fd_ulong_sat_add( acct_infos_va, acc_info_total_sz ) >= FD_VM_MEM_MAP_INPUT_REGION_START ) ) {
828 36 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_INVALID_POINTER );
829 36 : return FD_VM_SYSCALL_ERR_INVALID_POINTER;
830 36 : }
831 2247 : }
832 :
833 : /* This is the equivalent of translate_slice in translate_account_infos */
834 6174 : 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 );
835 :
836 : /* Right after translating, Agave checks the number of account infos */
837 6174 : if( FD_UNLIKELY( acct_info_cnt > get_cpi_max_account_infos() ) ) {
838 6 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_MAX_INSTRUCTION_ACCOUNT_INFOS_EXCEEDED );
839 6 : return FD_VM_SYSCALL_ERR_MAX_INSTRUCTION_ACCOUNT_INFOS_EXCEEDED;
840 6 : }
841 :
842 : /* Consume compute units proportional to the number of account infos */
843 3081 : ulong account_infos_bytes = fd_ulong_sat_mul( acct_info_cnt, FD_VM_ACCOUNT_INFO_BYTE_SIZE );
844 3081 : FD_VM_CU_UPDATE( vm, account_infos_bytes / FD_VM_CPI_BYTES_PER_UNIT );
845 :
846 3081 : fd_pubkey_t const * acct_info_keys[ FD_CPI_MAX_ACCOUNT_INFOS ];
847 7263 : for( ulong acct_idx = 0UL; acct_idx < acct_info_cnt; acct_idx++ ) {
848 : /* Translate each pubkey address specified in account_infos.
849 : Failed translation should lead to an access violation and
850 : implies that obviously bad account_info has been supplied. */
851 12558 : acct_info_keys[ acct_idx ] = FD_VM_MEM_HADDR_LD( vm, acc_infos[ acct_idx ].pubkey_addr, alignof(uchar), sizeof(fd_pubkey_t) );
852 12558 : }
853 :
854 : /* translate_accounts_common ***************************************************************
855 : https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L1049-L1193 */
856 3081 : fd_vm_cpi_translated_account_t translated_accounts[ FD_TXN_INSTR_ACCT_MAX ];
857 3069 : ulong translated_accounts_len = 0UL;
858 3069 : err = VM_SYSCALL_CPI_TRANSLATE_AND_UPDATE_ACCOUNTS_FUNC(
859 3069 : vm,
860 3069 : instruction_accounts,
861 3069 : instruction_accounts_cnt,
862 3069 : acct_infos_va,
863 3069 : acct_info_keys,
864 3069 : acc_infos,
865 3069 : acct_info_cnt,
866 3069 : translated_accounts,
867 3069 : &translated_accounts_len
868 3069 : );
869 : /* errors are propagated in the function itself. */
870 3069 : if( FD_UNLIKELY( err ) ) return err;
871 :
872 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L905-L928 */
873 2805 : if( vm->syscall_parameter_address_restrictions ) {
874 4674 : for( ulong i=0UL; i<translated_accounts_len; i++ ) {
875 2679 : fd_vm_cpi_translated_account_t * translated_account = &translated_accounts[i];
876 2679 : fd_guarded_borrowed_account_t callee_acc = {0};
877 2679 : FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( vm->instr_ctx, translated_account->index_in_caller, &callee_acc );
878 2679 : uchar update_caller = 0;
879 2679 : err = VM_SYCALL_CPI_UPDATE_CALLEE_ACC_FUNC( vm, &translated_account->caller_account, &callee_acc, &update_caller );
880 2679 : if( FD_UNLIKELY( err ) ) {
881 0 : return err;
882 0 : }
883 2679 : translated_account->update_caller_account_region =
884 2679 : (uchar)( translated_account->update_caller_account_info || update_caller );
885 2679 : }
886 1995 : }
887 :
888 : /* Set the transaction compute meter to be the same as the VM's compute meter,
889 : so that the callee cannot use compute units that the caller has already used. */
890 2805 : vm->instr_ctx->txn_out->details.compute_budget.compute_meter = vm->cu;
891 :
892 2805 : long const regime1 = fd_tickcount();
893 :
894 : /* Execute the CPI instruction in the runtime */
895 2805 : 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 );
896 2805 : ulong instr_exec_res = (ulong)err_exec;
897 :
898 2805 : long const regime2 = fd_tickcount();
899 2805 : vm->instr_ctx->runtime->metrics.cpi_setup_cum_ticks += (ulong)( regime1-regime0 );
900 :
901 : /* Set the CU meter to the instruction context's transaction context's compute meter,
902 : so that the caller can't use compute units that the callee has already used. */
903 2805 : vm->cu = vm->instr_ctx->txn_out->details.compute_budget.compute_meter;
904 :
905 2805 : *_ret = instr_exec_res;
906 :
907 : /* Errors are propagated in fd_execute_instr. */
908 2805 : if( FD_UNLIKELY( err_exec ) ) return err_exec;
909 :
910 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L942-L957 */
911 5262 : for( ulong i=0UL; i<translated_accounts_len; i++ ) {
912 3174 : fd_vm_cpi_translated_account_t * translated_account = &translated_accounts[i];
913 3174 : fd_guarded_borrowed_account_t callee_acc = {0};
914 3174 : FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( vm->instr_ctx, translated_account->index_in_caller, &callee_acc );
915 3174 : if( !translated_account->update_caller_account_info ) continue;
916 2790 : err = VM_SYSCALL_CPI_UPDATE_CALLER_ACC_FUNC( vm, &translated_account->caller_account, &callee_acc );
917 2790 : if( FD_UNLIKELY( err ) ) {
918 165 : return err;
919 165 : }
920 2790 : }
921 :
922 : /* With virtual_address_space_adjustments, update the caller's memory regions
923 : to reflect any changes the callee made to account data.
924 : https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/cpi.rs#L959-L973 */
925 2088 : if( vm->virtual_address_space_adjustments ) {
926 2370 : for( ulong i=0UL; i<translated_accounts_len; i++ ) {
927 1407 : fd_vm_cpi_translated_account_t * translated_account = &translated_accounts[i];
928 1407 : fd_guarded_borrowed_account_t borrowed_callee_acc = {0};
929 1407 : err = fd_exec_instr_ctx_try_borrow_instr_account( vm->instr_ctx, translated_account->index_in_caller, &borrowed_callee_acc );
930 1407 : if( FD_UNLIKELY( err ) ) return err;
931 1407 : if( !translated_account->update_caller_account_region ) continue;
932 :
933 1263 : err = fd_vm_cpi_update_caller_account_region( vm, translated_account, &borrowed_callee_acc );
934 1263 : if( FD_UNLIKELY( err ) ) {
935 0 : return err;
936 0 : }
937 1263 : }
938 963 : }
939 :
940 2088 : long const regime3 = fd_tickcount();
941 2088 : vm->instr_ctx->runtime->metrics.cpi_commit_cum_ticks += (ulong)( regime3-regime2 );
942 :
943 2088 : return FD_VM_SUCCESS;
944 2088 : }
945 :
946 : #undef VM_SYSCALL_CPI_UPDATE_CALLER_ACC_FUNC
947 : #undef VM_SYSCALL_CPI_FROM_ACC_INFO_FUNC
948 : #undef VM_SYSCALL_CPI_TRANSLATE_AND_UPDATE_ACCOUNTS_FUNC
949 : #undef VM_SYSCALL_CPI_INSTRUCTION_TO_INSTR_FUNC
950 : #undef VM_SYSCALL_CPI_FUNC
|