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