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