LCOV - code coverage report
Current view: top level - flamenco/vm/syscall - fd_vm_syscall_cpi_common.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 471 0.0 %
Date: 2025-08-05 05:04:49 Functions: 0 10 0.0 %

          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/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L23
      18             : 
      19             :    This is used for checking that the account info pointers given by the
      20             :    user match up with the addresses in the serialized account metadata.
      21             : 
      22             :    Field name length is restricted to 54 because
      23             :    127 - (37 + 18 + 18) leaves 54 characters for the field name
      24             :  */
      25             : #define VM_SYSCALL_CPI_CHECK_ACCOUNT_INFO_POINTER_FIELD_MAX_54(vm, vm_addr, expected_vm_addr, field_name) \
      26           0 :   if( FD_UNLIKELY( vm_addr!=expected_vm_addr )) {                                                         \
      27           0 :     fd_log_collector_printf_dangerous_max_127( vm->instr_ctx,                                             \
      28           0 :       "Invalid account info pointer `%s': %#lx != %#lx", field_name, vm_addr, expected_vm_addr );         \
      29           0 :     FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_INVALID_POINTER );                                   \
      30           0 :     return FD_VM_SYSCALL_ERR_INVALID_POINTER;                                                             \
      31           0 :   }
      32             : 
      33             : /* fd_vm_syscall_cpi_instruction_to_instr_{c/rust} takes the translated
      34             :    CPI ABI structures (instruction and account meta list), and uses these
      35             :    to populate a fd_instr_info_t struct. This struct can then be given to the
      36             :    FD runtime for execution.
      37             : 
      38             :    WARNING:  out_instr will be partially filled if there are unmatched account
      39             :    metas (i.e,. no corresponding entry in the transaction accounts list). This
      40             :    is not an error condition. fd_vm_prepare_instruction has to handle that case
      41             :    in order to match Agave's behavior of checking presence in both transaction
      42             :    accounts list and caller instruction accounts list in a single loop iteration.
      43             : 
      44             : Parameters:
      45             : - vm: handle to the vm
      46             : - cpi_instr: instruction to execute laid out in the CPI ABI format (Rust or C)
      47             : - cpi_acc_metas: list of account metas, again in the CPI ABI format
      48             : - signers: derived signers for this CPI call
      49             : - signers_cnt: length of the signers list
      50             : - cpi_instr_data: instruction data in host address space
      51             : 
      52             : TODO: return codes/errors?
      53             : */
      54           0 : #define VM_SYSCALL_CPI_INSTRUCTION_TO_INSTR_FUNC FD_EXPAND_THEN_CONCAT2(fd_vm_syscall_cpi_instruction_to_instr_, VM_SYSCALL_CPI_ABI)
      55             : static int
      56             : VM_SYSCALL_CPI_INSTRUCTION_TO_INSTR_FUNC( fd_vm_t *                         vm,
      57             :                                           VM_SYSCALL_CPI_INSTR_T const *    cpi_instr,
      58             :                                           VM_SYSCALL_CPI_ACC_META_T const * cpi_acct_metas,
      59             :                                           fd_pubkey_t const *               program_id,
      60             :                                           uchar const *                     cpi_instr_data,
      61             :                                           fd_instr_info_t *                 out_instr,
      62           0 :                                           fd_pubkey_t                       out_instr_acct_keys[ FD_INSTR_ACCT_MAX ] ) {
      63           0 :   out_instr->program_id = UCHAR_MAX;
      64           0 :   out_instr->data_sz    = (ushort)VM_SYSCALL_CPI_INSTR_DATA_LEN( cpi_instr );
      65           0 :   out_instr->data       = (uchar *)cpi_instr_data;
      66           0 :   out_instr->acct_cnt   = (ushort)VM_SYSCALL_CPI_INSTR_ACCS_LEN( cpi_instr );
      67             : 
      68             :   /* Find the index of the CPI instruction's program account in the transaction */
      69           0 :   int program_id_idx = fd_exec_txn_ctx_find_index_of_account( vm->instr_ctx->txn_ctx, program_id );
      70           0 :   if( FD_LIKELY( program_id_idx != -1 ) ) {
      71           0 :     out_instr->program_id = (uchar)program_id_idx;
      72           0 :   }
      73             : 
      74           0 :   uchar acc_idx_seen[ FD_INSTR_ACCT_MAX ] = {0};
      75             : 
      76           0 :   for( ushort i=0; i<VM_SYSCALL_CPI_INSTR_ACCS_LEN( cpi_instr ); i++ ) {
      77           0 :     VM_SYSCALL_CPI_ACC_META_T const * cpi_acct_meta = &cpi_acct_metas[i];
      78           0 :     fd_pubkey_t const * pubkey = fd_type_pun_const( VM_SYSCALL_CPI_ACC_META_PUBKEY( vm, cpi_acct_meta ) );
      79           0 :     out_instr_acct_keys[i] = *pubkey;
      80             : 
      81             :     /* The parent flag(s) for is writable/signer is checked in
      82             :        fd_vm_prepare_instruction. Signer privilege is allowed iff the account
      83             :        is a signer in the caller or if it is a derived signer. */
      84             :     /* TODO: error if flags are wrong */
      85           0 :     out_instr->accounts[i] = fd_instruction_account_init( USHORT_MAX,
      86           0 :                                                           USHORT_MAX,
      87           0 :                                                           USHORT_MAX,
      88           0 :                                                           VM_SYSCALL_CPI_ACC_META_IS_WRITABLE( cpi_acct_meta ),
      89           0 :                                                           VM_SYSCALL_CPI_ACC_META_IS_SIGNER( cpi_acct_meta ) );
      90             : 
      91             :     /* Use USHORT_MAX to indicate account not found
      92             :         https://github.com/anza-xyz/agave/blob/v2.1.14/program-runtime/src/invoke_context.rs#L366-L370 */
      93           0 :     int idx_in_txn    = fd_exec_txn_ctx_find_index_of_account( vm->instr_ctx->txn_ctx, pubkey );
      94           0 :     int idx_in_caller = fd_exec_instr_ctx_find_idx_of_instr_account( vm->instr_ctx, pubkey );
      95             : 
      96           0 :     fd_instr_info_setup_instr_account( out_instr,
      97           0 :                                        acc_idx_seen,
      98           0 :                                        idx_in_txn!=-1 ? (ushort)idx_in_txn : USHORT_MAX,
      99           0 :                                        idx_in_caller!=-1 ? (ushort)idx_in_caller : USHORT_MAX,
     100           0 :                                        i,
     101           0 :                                        VM_SYSCALL_CPI_ACC_META_IS_WRITABLE( cpi_acct_meta ),
     102           0 :                                        VM_SYSCALL_CPI_ACC_META_IS_SIGNER( cpi_acct_meta ) );
     103             : 
     104           0 :   }
     105             : 
     106           0 :   return FD_VM_SUCCESS;
     107           0 : }
     108             : 
     109             : /*
     110             : fd_vm_syscall_cpi_update_callee_acc_{rust/c} corresponds to solana_bpf_loader_program::syscalls::cpi::update_callee_account:
     111             : https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/programs/bpf_loader/src/syscalls/cpi.rs#L1302
     112             : 
     113             : (the copy of the account stored in the instruction context's
     114             : borrowed accounts cache)
     115             : 
     116             : This function should be called before the CPI instruction is executed. Its purpose is to
     117             : update the callee account's view of the given account with any changes the caller may made
     118             : to the account before the CPI instruction is executed.
     119             : 
     120             : The callee's view of the account is the borrowed accounts cache, so to update the
     121             : callee account we look up the account in the borrowed accounts cache and update it.
     122             : 
     123             : Paramaters:
     124             : - vm: pointer to the virtual machine handle
     125             : - account_info: account info object
     126             : - callee_acc_pubkey: pubkey of the account. this is used to look up the account in the borrowed accounts cache
     127             :   (TODO: this seems redundant? we can probably remove this, as the account_info contains the pubkey)
     128             : */
     129           0 : #define VM_SYCALL_CPI_UPDATE_CALLEE_ACC_FUNC FD_EXPAND_THEN_CONCAT2(fd_vm_syscall_cpi_update_callee_acc_, VM_SYSCALL_CPI_ABI)
     130             : static int
     131             : VM_SYCALL_CPI_UPDATE_CALLEE_ACC_FUNC( fd_vm_t *                          vm,
     132             :                                       fd_vm_cpi_caller_account_t const * caller_account,
     133           0 :                                       uchar                              instr_acc_idx ) {
     134           0 :   int err;
     135             : 
     136             :   /* Borrow the callee account.
     137             :      TODO: Agave borrows before this function call. Consider refactoring to borrow the account at the same place as Agave.
     138             :      https://github.com/anza-xyz/agave/blob/v2.1.14/programs/bpf_loader/src/syscalls/cpi.rs#L893 */
     139           0 :   fd_guarded_borrowed_account_t callee_acc;
     140           0 :   err = fd_exec_instr_ctx_try_borrow_instr_account( vm->instr_ctx, instr_acc_idx, &callee_acc );
     141           0 :   if( FD_UNLIKELY( err ) ) {
     142             :     /* No need to do anything if the account is missing from the borrowed accounts cache */
     143           0 :     return FD_VM_SUCCESS;
     144           0 :   }
     145             : 
     146           0 :   if( fd_borrowed_account_get_lamports( &callee_acc )!=*(caller_account->lamports) ) {
     147           0 :     err = fd_borrowed_account_set_lamports( &callee_acc, *(caller_account->lamports) );
     148           0 :     if( FD_UNLIKELY( err ) ) {
     149           0 :       FD_VM_ERR_FOR_LOG_INSTR( vm, err );
     150           0 :       return -1;
     151           0 :     }
     152           0 :   }
     153             : 
     154           0 :   if( !vm->direct_mapping ) {
     155             :     /* Get the account data */
     156             :     /* Update the account data, if the account data can be changed */
     157             :     /* FIXME: double-check these permissions, especially the callee_acc_idx */
     158             : 
     159           0 :     if( fd_borrowed_account_can_data_be_resized( &callee_acc, caller_account->serialized_data_len, &err ) &&
     160           0 :         fd_borrowed_account_can_data_be_changed( &callee_acc, &err ) ) {
     161             :         /* We must ignore the errors here, as they are informational and do not mean the result is invalid. */
     162             :         /* TODO: not pass informational errors like this? */
     163             : 
     164           0 :       err = fd_borrowed_account_set_data_from_slice( &callee_acc, caller_account->serialized_data, caller_account->serialized_data_len );
     165           0 :       if( FD_UNLIKELY( err ) ) {
     166           0 :         FD_VM_ERR_FOR_LOG_INSTR( vm, err );
     167           0 :         return -1;
     168           0 :       }
     169           0 :     } else if( FD_UNLIKELY( caller_account->serialized_data_len!=fd_borrowed_account_get_data_len( &callee_acc ) ||
     170           0 :                             memcmp( fd_borrowed_account_get_data( &callee_acc ), caller_account->serialized_data, caller_account->serialized_data_len ) ) ) {
     171           0 :       FD_VM_ERR_FOR_LOG_INSTR( vm, err );
     172           0 :       return -1;
     173           0 :     }
     174             : 
     175           0 :   } else { /* Direct mapping enabled */
     176           0 :     ulong * ref_to_len = FD_VM_MEM_HADDR_ST( vm, caller_account->ref_to_len_in_vm.vaddr, alignof(ulong), sizeof(ulong) );
     177           0 :     ulong   orig_len   = caller_account->orig_data_len;
     178           0 :     ulong   prev_len   = fd_borrowed_account_get_data_len( &callee_acc );
     179           0 :     ulong   post_len   = *ref_to_len;
     180             : 
     181           0 :     int err;
     182           0 :     if( fd_borrowed_account_can_data_be_resized( &callee_acc, post_len, &err ) &&
     183           0 :         fd_borrowed_account_can_data_be_changed( &callee_acc, &err ) ) {
     184             : 
     185           0 :       ulong realloc_bytes_used = fd_ulong_sat_sub( post_len, orig_len );
     186             : 
     187           0 :       if( FD_UNLIKELY( vm->is_deprecated && realloc_bytes_used ) ) {
     188           0 :         FD_VM_ERR_FOR_LOG_INSTR( vm, FD_EXECUTOR_INSTR_ERR_INVALID_REALLOC );
     189           0 :         return -1;
     190           0 :       }
     191             : 
     192           0 :       err = fd_borrowed_account_set_data_length( &callee_acc, post_len );
     193           0 :       if( FD_UNLIKELY( err ) ) {
     194           0 :         FD_VM_ERR_FOR_LOG_INSTR( vm, err );
     195           0 :         return -1;
     196           0 :       }
     197             : 
     198             : 
     199           0 :       if( realloc_bytes_used ) {
     200             :         /* We need to get the relevant data slice. However, we know that the
     201             :            current length currently exceeds the original length for the account
     202             :            data. This means that all of the additional bytes must exist in the
     203             :            account data resizing region. As an invariant, original_len must be
     204             :            equal to the length of the account data region. This means we can
     205             :            smartly look up the right region and don't need to worry about
     206             :            multiple region access.We just need to load in the bytes from
     207             :            (original len, post_len]. */
     208           0 :         uchar const * realloc_data = FD_VM_MEM_SLICE_HADDR_LD( vm, caller_account->vm_data_vaddr+orig_len, alignof(uchar), realloc_bytes_used );
     209             : 
     210           0 :         uchar * data = NULL;
     211           0 :         ulong   dlen = 0UL;
     212           0 :         err = fd_borrowed_account_get_data_mut( &callee_acc, &data, &dlen );
     213           0 :         if( FD_UNLIKELY( err ) ) {
     214           0 :           FD_VM_ERR_FOR_LOG_INSTR( vm, err );
     215           0 :           return -1;
     216           0 :         }
     217           0 :         fd_memcpy( data+orig_len, realloc_data, realloc_bytes_used );
     218           0 :       }
     219             : 
     220           0 :     } else if( FD_UNLIKELY( prev_len!=post_len ) ) {
     221           0 :       FD_VM_ERR_FOR_LOG_INSTR( vm, err );
     222           0 :       return -1;
     223           0 :     }
     224           0 :   }
     225             : 
     226           0 :   if( FD_UNLIKELY( memcmp( fd_borrowed_account_get_owner( &callee_acc ), caller_account->owner, sizeof(fd_pubkey_t) ) ) ) {
     227           0 :     err = fd_borrowed_account_set_owner( &callee_acc, caller_account->owner );
     228           0 :     if( FD_UNLIKELY( err ) ) {
     229           0 :       FD_VM_ERR_FOR_LOG_INSTR( vm, err );
     230           0 :       return -1;
     231           0 :     }
     232           0 :   }
     233             : 
     234           0 :   return FD_VM_SUCCESS;
     235           0 : }
     236             : 
     237             : /*
     238             : fd_vm_syscall_cpi_translate_and_update_accounts_ mirrors the behaviour of
     239             : solana_bpf_loader_program::syscalls::cpi::translate_and_update_accounts:
     240             : https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/programs/bpf_loader/src/syscalls/cpi.rs#L954-L1085
     241             : 
     242             : It translates the caller accounts to the host address space, and then calls
     243             : fd_vm_syscall_cpi_update_callee_acc to update the callee borrowed account with any changes
     244             : the caller has made to the account during execution before this CPI call.
     245             : 
     246             : It also populates the out_callee_indices and out_caller_indices arrays:
     247             : - out_callee_indices: indices of the callee accounts in the transaction
     248             : - out_caller_indices: indices of the caller accounts in the account_infos array
     249             : 
     250             : Parameters:
     251             : - vm: pointer to the virtual machine handle
     252             : - instruction_accounts: array of instruction accounts
     253             : - instruction_accounts_cnt: length of the instruction_accounts array
     254             : - account_infos: array of account infos
     255             : - account_infos_length: length of the account_infos array
     256             : 
     257             : Populates the given out_callee_indices and out_caller_indices arrays:
     258             : - out_callee_indices: indices of the callee accounts in the transaction
     259             : - out_caller_indices: indices of the caller accounts in the account_infos array
     260             : - out_len: length of the out_callee_indices and out_caller_indices arrays
     261             : */
     262           0 : #define VM_SYSCALL_CPI_TRANSLATE_AND_UPDATE_ACCOUNTS_FUNC FD_EXPAND_THEN_CONCAT2(fd_vm_syscall_cpi_translate_and_update_accounts_, VM_SYSCALL_CPI_ABI)
     263             : static int
     264             : VM_SYSCALL_CPI_TRANSLATE_AND_UPDATE_ACCOUNTS_FUNC(
     265             :                               fd_vm_t *                         vm,
     266             :                               fd_instruction_account_t const *  instruction_accounts,
     267             :                               ulong const                       instruction_accounts_cnt,
     268             :                               ulong                             acct_infos_va,
     269             :                               fd_pubkey_t const * *             account_info_keys, /* same length as account_infos_length */
     270             :                               VM_SYSCALL_CPI_ACC_INFO_T const * account_infos,
     271             :                               ulong const                       account_infos_length,
     272             :                               ushort *                          out_callee_indices,
     273             :                               ushort *                          out_caller_indices,
     274             :                               fd_vm_cpi_caller_account_t *      caller_accounts,
     275           0 :                               ulong *                           out_len ) {
     276           0 :   for( ulong i=0UL; i<instruction_accounts_cnt; i++ ) {
     277           0 :     if( i!=instruction_accounts[i].index_in_callee ) {
     278             :       /* Skip duplicate accounts */
     279           0 :       continue;
     280           0 :     }
     281             : 
     282             :     /* `fd_vm_prepare_instruction()` will always set up a valid index for `index_in_caller`, so we can access the borrowed account directly.
     283             :        A borrowed account will always have non-NULL meta (if the account doesn't exist, `fd_executor_setup_accounts_for_txn()`
     284             :        will set its meta up) */
     285             : 
     286             :     /* https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/programs/bpf_loader/src/syscalls/cpi.rs#L878-L881 */
     287           0 :     fd_guarded_borrowed_account_t callee_acct;
     288           0 :     FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( vm->instr_ctx, instruction_accounts[i].index_in_caller, &callee_acct );
     289             : 
     290           0 :     fd_pubkey_t const *       account_key = callee_acct.acct->pubkey;
     291           0 :     fd_account_meta_t const * acc_meta    = fd_borrowed_account_get_acc_meta( &callee_acct );
     292             : 
     293             :     /* If the account is known and executable, we only need to consume the compute units.
     294             :        Executable accounts can't be modified, so we don't need to update the callee account. */
     295           0 :     if( fd_borrowed_account_is_executable( &callee_acct ) ) {
     296             :       // FIXME: should this be FD_VM_CU_MEM_UPDATE? Changing this changes the CU behaviour from main (because of the base cost)
     297           0 :       FD_VM_CU_UPDATE( vm, acc_meta->dlen / FD_VM_CPI_BYTES_PER_UNIT );
     298           0 :       continue;
     299           0 :     }
     300             : 
     301             :     /* FIXME: we should not need to drop the account here to avoid a double borrow.
     302             :        Instead, we should borrow the account before entering this function. */
     303           0 :     fd_borrowed_account_drop( &callee_acct );
     304             : 
     305             :     /* Find the indicies of the account in the caller and callee instructions */
     306           0 :     uint found = 0;
     307           0 :     for( ushort j=0; j<account_infos_length && !found; j++ ) {
     308           0 :       fd_pubkey_t const * acct_addr = account_info_keys[ j ];
     309             :       /* https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L912
     310             :        */
     311           0 :       if( memcmp( account_key->uc, acct_addr->uc, sizeof(fd_pubkey_t) ) != 0 ) {
     312           0 :         continue;
     313           0 :       }
     314             : 
     315             : 
     316             :       /* The following error is practically unreachable because
     317             :          essentially the same check is performed in
     318             :          prepare_instruction().  So a missing account error would have
     319             :          been returned then and there.  Hence, we are skipping this
     320             :          duplicate check here.
     321             :          https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L914-L923
     322             :        */
     323             : 
     324             : 
     325             :       /* The next iteration will overwrite this if it turns out that we
     326             :          do not need to preserve this for update_caller().
     327             :        */
     328           0 :       fd_vm_cpi_caller_account_t * caller_account = caller_accounts + *out_len;
     329             :       /* Record the indicies of this account */
     330           0 :       ushort index_in_caller = instruction_accounts[i].index_in_caller;
     331           0 :       if (instruction_accounts[i].is_writable) {
     332           0 :         out_callee_indices[*out_len] = index_in_caller;
     333           0 :         out_caller_indices[*out_len] = j;
     334           0 :         (*out_len)++;
     335           0 :       }
     336           0 :       found = 1;
     337             : 
     338             :       /* Logically this check isn't ever going to fail due to how the
     339             :          account_info_keys array is set up.  We replicate the check for
     340             :          clarity and also to guard against accidental violation of the
     341             :          assumed invariant in the future.
     342             :          https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L926-L928
     343             :        */
     344           0 :       if( FD_UNLIKELY( j >= account_infos_length ) ) {
     345           0 :         FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_INVALID_LENGTH );
     346           0 :         return FD_VM_SYSCALL_ERR_INVALID_LENGTH;
     347           0 :       }
     348             : 
     349             :       /* The following implements the checks in from_account_info which
     350             :          is invoked as do_translate() in translate_and_update_accounts()
     351             :          https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L931
     352             :        */
     353             :       ////// BEGIN from_account_info
     354             : 
     355           0 :       fd_vm_acc_region_meta_t * acc_region_meta = &vm->acc_region_metas[index_in_caller];
     356           0 :       if( FD_LIKELY( vm->direct_mapping ) ) {
     357             :         /* https://github.com/anza-xyz/agave/blob/v2.1.7/programs/bpf_loader/src/syscalls/cpi.rs#L116
     358             :          */
     359           0 :         ulong expected_pubkey_vaddr = serialized_pubkey_vaddr( vm, acc_region_meta );
     360             :         /* Max msg_sz: 40 + 18 + 18 = 76 < 127 */
     361           0 :         VM_SYSCALL_CPI_CHECK_ACCOUNT_INFO_POINTER_FIELD_MAX_54(vm, account_infos[j].pubkey_addr, expected_pubkey_vaddr, "key");
     362             : 
     363             :         /* https://github.com/anza-xyz/agave/blob/v2.1.7/programs/bpf_loader/src/syscalls/cpi.rs#L122
     364             :          */
     365           0 :         ulong expected_owner_vaddr = serialized_owner_vaddr( vm, acc_region_meta );
     366             :         /* Max msg_sz: 42 + 18 + 18 = 78 < 127 */
     367           0 :         VM_SYSCALL_CPI_CHECK_ACCOUNT_INFO_POINTER_FIELD_MAX_54(vm, account_infos[j].owner_addr, expected_owner_vaddr, "owner");
     368           0 :       }
     369             : 
     370             :       /* https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L134
     371             :        */
     372           0 :       VM_SYSCALL_CPI_ACC_INFO_LAMPORTS_VADDR( vm, (account_infos + j), lamports_vaddr );
     373           0 :       if( FD_LIKELY( vm->direct_mapping ) ) {
     374             :         /* https://github.com/anza-xyz/agave/blob/v2.1.7/programs/bpf_loader/src/syscalls/cpi.rs#L140
     375             :            Check that the account's lamports Rc<RefCell<&mut u64>> is
     376             :            not stored in the account.
     377             :            Because a refcell is only present if the Rust SDK is used, we
     378             :            only need to check this for the Rust ABI.
     379             :         */
     380             :         #ifdef VM_SYSCALL_CPI_ACC_INFO_LAMPORTS_RC_REFCELL_VADDR
     381           0 :         VM_SYSCALL_CPI_ACC_INFO_LAMPORTS_RC_REFCELL_VADDR( vm, (account_infos + j), lamports_rc_vaddr )
     382           0 :         if ( FD_UNLIKELY( lamports_rc_vaddr >= FD_VM_MEM_MAP_INPUT_REGION_START ) ) {
     383           0 :           FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_INVALID_POINTER );
     384           0 :           return FD_VM_SYSCALL_ERR_INVALID_POINTER;
     385           0 :         }
     386           0 :         #endif
     387             : 
     388             :         /* https://github.com/anza-xyz/agave/blob/v2.1.7/programs/bpf_loader/src/syscalls/cpi.rs#L144
     389             :          */
     390           0 :         ulong expected_lamports_vaddr = serialized_lamports_vaddr( vm, acc_region_meta );
     391             :         /* Max msg_sz: 45 + 18 + 18 = 81 < 127 */
     392           0 :         VM_SYSCALL_CPI_CHECK_ACCOUNT_INFO_POINTER_FIELD_MAX_54(vm, lamports_vaddr, expected_lamports_vaddr, "lamports");
     393           0 :       }
     394             :       /* https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L151
     395             :        */
     396           0 :       VM_SYSCALL_CPI_ACC_INFO_LAMPORTS( vm, (account_infos + j), lamports_haddr );
     397           0 :       caller_account->lamports = lamports_haddr;
     398             : 
     399             :       /* https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L154
     400             :        */
     401           0 :       caller_account->owner = FD_VM_MEM_HADDR_ST( vm, (account_infos + j)->owner_addr, alignof(uchar), sizeof(fd_pubkey_t) );
     402             : 
     403           0 :       if( FD_LIKELY( vm->direct_mapping ) ) {
     404             :         /* https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L161
     405             :            Check that the account's data Rc<RefCell<T>> is not stored in
     406             :            the account.
     407             :            Because a refcell is only present if the Rust SDK is used, we
     408             :            only need to check this for the Rust ABI.
     409             :          */
     410             :         #ifdef VM_SYSCALL_CPI_ACC_INFO_DATA_RC_REFCELL_VADDR
     411           0 :         VM_SYSCALL_CPI_ACC_INFO_DATA_RC_REFCELL_VADDR( vm, (account_infos + j), data_rc_vaddr )
     412           0 :         if( FD_UNLIKELY( data_rc_vaddr >= FD_VM_MEM_MAP_INPUT_REGION_START ) ) {
     413           0 :           FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_INVALID_POINTER );
     414           0 :           return FD_VM_SYSCALL_ERR_INVALID_POINTER;
     415           0 :         }
     416             :         #endif
     417           0 :       }
     418             : 
     419             :       /* https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L166
     420             :        */
     421           0 :       VM_SYSCALL_CPI_ACC_INFO_DATA_VADDR( vm, (account_infos + j), data_vaddr );
     422             : 
     423           0 :       if( FD_LIKELY( vm->direct_mapping ) ) {
     424             :         /* https://github.com/anza-xyz/agave/blob/v2.1.7/programs/bpf_loader/src/syscalls/cpi.rs#L172 */
     425           0 :         ulong expected_data_region_vaddr = FD_VM_MEM_MAP_INPUT_REGION_START +
     426           0 :           vm->input_mem_regions[acc_region_meta->region_idx].vaddr_offset;
     427             :         /* Max msg_sz: 41 + 18 + 18 = 77 < 127 */
     428           0 :         VM_SYSCALL_CPI_CHECK_ACCOUNT_INFO_POINTER_FIELD_MAX_54(vm, data_vaddr, expected_data_region_vaddr, "data");
     429           0 :       }
     430             : 
     431             :       /* https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L180
     432             :        */
     433           0 :       VM_SYSCALL_CPI_SET_ACC_INFO_DATA_GET_LEN( vm, (account_infos + j), data_vaddr );
     434           0 :       FD_VM_CU_UPDATE( vm, data_vaddr_len / FD_VM_CPI_BYTES_PER_UNIT );
     435             : 
     436             :       /* https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L187
     437             :          https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L331
     438             :        */
     439             :       #ifdef VM_SYSCALL_CPI_ACC_INFO_DATA_LEN_VADDR
     440             :       /* Rust ABI */
     441           0 :       VM_SYSCALL_CPI_ACC_INFO_DATA_LEN_VADDR( vm, (account_infos + j), data_len_vaddr );
     442             :       (void)acct_infos_va;
     443             :       #else
     444             :       /* C ABI */
     445           0 :       ulong data_len_vaddr = vm_syscall_cpi_data_len_vaddr_c(
     446           0 :         fd_ulong_sat_add( acct_infos_va, fd_ulong_sat_mul( j, VM_SYSCALL_CPI_ACC_INFO_SIZE ) ),
     447             :         (ulong)&((account_infos + j)->data_sz),
     448             :         (ulong)(account_infos + j)
     449             :       );
     450             :       #endif
     451           0 :       if( FD_LIKELY( vm->direct_mapping ) ) {
     452             :         #ifdef VM_SYSCALL_CPI_ACC_INFO_DATA_LEN_VADDR
     453             :         /* https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L193
     454             :          */
     455           0 :         if( FD_UNLIKELY( data_len_vaddr >= FD_VM_MEM_MAP_INPUT_REGION_START ) ) {
     456           0 :           FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_INVALID_POINTER );
     457           0 :           return FD_VM_SYSCALL_ERR_INVALID_POINTER;
     458           0 :         }
     459           0 :         #endif
     460           0 :         caller_account->ref_to_len_in_vm.vaddr = data_len_vaddr;
     461           0 :       }
     462           0 :       if( FD_UNLIKELY( !vm->direct_mapping ) ) {
     463           0 :         ulong * data_len = FD_VM_MEM_HADDR_ST(
     464           0 :           vm,
     465           0 :           data_len_vaddr,
     466           0 :           1UL,
     467           0 :           sizeof(ulong)
     468           0 :         );
     469           0 :         caller_account->ref_to_len_in_vm.translated = data_len;
     470           0 :       }
     471           0 :       caller_account->vm_data_vaddr   = data_vaddr;
     472             : 
     473             :       /* https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L228
     474             :        */
     475           0 :       caller_account->serialized_data = NULL;
     476           0 :       if( FD_UNLIKELY( !vm->direct_mapping ) ) {
     477           0 :         VM_SYSCALL_CPI_ACC_INFO_DATA( vm, (account_infos + j), data_haddr );
     478           0 :         (void)data_haddr_vm_addr;
     479           0 :         caller_account->serialized_data     = data_haddr;
     480           0 :         caller_account->serialized_data_len = data_haddr_len;
     481           0 :       }
     482             : 
     483           0 :       caller_account->orig_data_len = acc_region_meta->original_data_len;
     484             : 
     485             :       ////// END from_account_info
     486             : 
     487             :       // TODO We should be able to cache the results of translation and reuse them in the update function.
     488             :       /* Update the callee account to reflect any changes the caller has made */
     489           0 :       int err = VM_SYCALL_CPI_UPDATE_CALLEE_ACC_FUNC( vm, caller_account, (uchar)index_in_caller );
     490           0 :       if( FD_UNLIKELY( err ) ) {
     491             :         /* errors are propagated in the function itself. */
     492           0 :         return err;
     493           0 :       }
     494           0 :     }
     495             : 
     496           0 :     if( !found ) {
     497             :       /* https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L966 */
     498           0 :       FD_BASE58_ENCODE_32_BYTES( account_key->uc, id_b58 );
     499           0 :       fd_log_collector_msg_many( vm->instr_ctx, 2, "Instruction references an unknown account ", 42UL, id_b58, id_b58_len );
     500           0 :       FD_VM_ERR_FOR_LOG_INSTR( vm, FD_EXECUTOR_INSTR_ERR_MISSING_ACC );
     501           0 :       return FD_EXECUTOR_INSTR_ERR_MISSING_ACC;
     502           0 :     }
     503           0 :   }
     504             : 
     505           0 :   return FD_VM_SUCCESS;
     506           0 : }
     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/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/programs/bpf_loader/src/syscalls/cpi.rs#L1291
     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             : Paramaters:
     520             : - vm: handle to the vm
     521             : - caller_acc_info: caller account info object, which should be updated
     522             : - pubkey: pubkey of the account
     523             : 
     524             : TODO: error codes
     525             : */
     526           0 : #define VM_SYSCALL_CPI_UPDATE_CALLER_ACC_FUNC FD_EXPAND_THEN_CONCAT2(fd_vm_cpi_update_caller_acc_, VM_SYSCALL_CPI_ABI)
     527             : static int
     528             : VM_SYSCALL_CPI_UPDATE_CALLER_ACC_FUNC( fd_vm_t *                          vm,
     529             :                                        VM_SYSCALL_CPI_ACC_INFO_T const *  caller_acc_info,
     530             :                                        fd_vm_cpi_caller_account_t const * caller_account,
     531             :                                        uchar                              instr_acc_idx,
     532           0 :                                        fd_pubkey_t const *                pubkey ) {
     533           0 :   int err;
     534             : 
     535           0 :   if( !vm->direct_mapping ) {
     536             :     /* Look up the borrowed account from the instruction context, which will contain
     537             :       the callee's changes.
     538             :       TODO: Agave borrows before entering this function. We should consider doing the same.
     539             :       https://github.com/anza-xyz/agave/blob/v2.1.14/programs/bpf_loader/src/syscalls/cpi.rs#L1168-L1169 */
     540           0 :     fd_guarded_borrowed_account_t borrowed_callee_acc;
     541           0 :     err = fd_exec_instr_ctx_try_borrow_instr_account_with_key( vm->instr_ctx, pubkey, &borrowed_callee_acc );
     542           0 :     if( FD_UNLIKELY( err && ( err != FD_ACC_MGR_ERR_UNKNOWN_ACCOUNT ) ) ) {
     543           0 :       return 1;
     544           0 :     }
     545             : 
     546           0 :     fd_txn_account_t * callee_acc = borrowed_callee_acc.acct;
     547             :     /* Update the caller account lamports with the value from the callee */
     548           0 :     *(caller_account->lamports) = callee_acc->vt->get_lamports( callee_acc );
     549             : 
     550             :     /* Update the caller account owner with the value from the callee */
     551           0 :     fd_pubkey_t const * updated_owner = callee_acc->vt->get_owner( callee_acc );
     552           0 :     if( updated_owner ) *caller_account->owner = *updated_owner;
     553           0 :     else                fd_memset( caller_account->owner, 0,             sizeof(fd_pubkey_t) );
     554             : 
     555             :     /* Update the caller account data with the value from the callee */
     556           0 :     VM_SYSCALL_CPI_ACC_INFO_DATA( vm, caller_acc_info, caller_acc_data );
     557             : 
     558           0 :     ulong const updated_data_len = callee_acc->vt->get_data_len( callee_acc );
     559           0 :     if( !updated_data_len ) fd_memset( (void*)caller_acc_data, 0, caller_acc_data_len );
     560           0 :     ulong * ref_to_len = caller_account->ref_to_len_in_vm.translated;
     561           0 :     if( *ref_to_len != updated_data_len ) {
     562           0 :       ulong max_increase = (vm->direct_mapping && vm->is_deprecated) ? 0UL : MAX_PERMITTED_DATA_INCREASE;
     563             :       // https://github.com/anza-xyz/agave/blob/7f3a6cf6d3c2dcc81bb38e49a5c9ef998a6f4dd9/programs/bpf_loader/src/syscalls/cpi.rs#L1387-L1397
     564           0 :       if( FD_UNLIKELY( updated_data_len>fd_ulong_sat_add( caller_account->orig_data_len, max_increase ) ) ) {
     565           0 :         FD_VM_ERR_FOR_LOG_INSTR( vm, FD_EXECUTOR_INSTR_ERR_INVALID_REALLOC);
     566           0 :         return FD_EXECUTOR_INSTR_ERR_INVALID_REALLOC;
     567           0 :       }
     568             : 
     569             :       /* FIXME: do we need to zero the memory that was previously used, if the new data_len is smaller?
     570             :       https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/programs/bpf_loader/src/syscalls/cpi.rs#L1361
     571             :         I don't think we do but need to double-check. */
     572             : 
     573             :       /* https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L1453
     574             :        */
     575           0 :       caller_acc_data = FD_VM_MEM_SLICE_HADDR_ST( vm, caller_acc_data_vm_addr, alignof(uchar), updated_data_len );
     576             : 
     577           0 :       *ref_to_len = updated_data_len;
     578             : 
     579             :       /* Update the serialized len field
     580             :         https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/programs/bpf_loader/src/syscalls/cpi.rs#L1437 */
     581           0 :       ulong * caller_len = FD_VM_MEM_HADDR_ST( vm, fd_ulong_sat_sub(caller_acc_data_vm_addr, sizeof(ulong)), alignof(ulong), sizeof(ulong) );
     582           0 :       *caller_len = updated_data_len;
     583             : 
     584             :       /* FIXME return instruction error account data size too small in the same scenarios solana does
     585             :         https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/programs/bpf_loader/src/syscalls/cpi.rs#L1534 */
     586           0 :     }
     587             : 
     588           0 :     fd_memcpy( caller_acc_data, callee_acc->vt->get_data( callee_acc ), updated_data_len );
     589           0 :   } else { /* Direct mapping enabled */
     590             : 
     591             :     /* Look up the borrowed account from the instruction context, which will
     592             :        contain the callee's changes.
     593             :        TODO: Agave borrows before entering this function. We should consider doing the same.
     594             :        https://github.com/anza-xyz/agave/blob/v2.1.14/programs/bpf_loader/src/syscalls/cpi.rs#L1168-L1169 */
     595           0 :     fd_guarded_borrowed_account_t borrowed_callee_acc;
     596           0 :     err = fd_exec_instr_ctx_try_borrow_instr_account_with_key( vm->instr_ctx, pubkey, &borrowed_callee_acc );
     597           0 :     if( FD_UNLIKELY( err && ( err != FD_ACC_MGR_ERR_UNKNOWN_ACCOUNT ) ) ) {
     598           0 :       return 1;
     599           0 :     }
     600             : 
     601           0 :     fd_txn_account_t * callee_acc = borrowed_callee_acc.acct;
     602             : 
     603             :     /* Update the caller account lamports with the value from the callee */
     604           0 :     *(caller_account->lamports) = callee_acc->vt->get_lamports( callee_acc );
     605             : 
     606             :     /* Update the caller account owner with the value from the callee */
     607           0 :     fd_pubkey_t const * updated_owner = callee_acc->vt->get_owner( callee_acc );
     608           0 :     if( updated_owner ) {
     609           0 :       *caller_account->owner = *updated_owner;
     610           0 :     } else {
     611           0 :       fd_memset( caller_account->owner, 0,             sizeof(fd_pubkey_t) );
     612           0 :     }
     613             : 
     614             :     /* Make sure that the capacity of the borrowed account is sized up in case
     615             :        it was shrunk in the CPI. It needs to be sized up in order to fit within
     616             :        the originally delinated regions when the account data was serialized.
     617             :        https://github.com/anza-xyz/agave/blob/36323b6dcd3e29e4d6fe6d73d716a3f33927148b/programs/bpf_loader/src/syscalls/cpi.rs#L1311 */
     618           0 :     VM_SYSCALL_CPI_ACC_INFO_METADATA( vm, caller_acc_info, caller_acc_data );
     619           0 :     ulong original_len = caller_account->orig_data_len;
     620             : 
     621           0 :     uchar zero_all_mapped_spare_capacity = 0;
     622             :     /* This case can only be triggered if the original length is more than 0 */
     623           0 :     if( callee_acc->vt->get_data_len( callee_acc ) < original_len ) {
     624           0 :       ulong new_len = callee_acc->vt->get_data_len( callee_acc );
     625             :       /* Allocate into the buffer to make sure that the original data len
     626             :          is still valid but don't change the dlen. Zero out the rest of the
     627             :          memory which is not used. */
     628           0 :       callee_acc->vt->resize( callee_acc, original_len );
     629           0 :       callee_acc->vt->set_data_len( callee_acc, new_len );
     630           0 :       zero_all_mapped_spare_capacity = 1;
     631           0 :     }
     632             : 
     633             :     /* Update the account data region if an account data region exists. We
     634             :        know that one exists iff the original len was non-zero. */
     635           0 :     ulong acc_region_idx = vm->acc_region_metas[instr_acc_idx].region_idx;
     636           0 :     if( original_len && vm->input_mem_regions[ acc_region_idx ].haddr!=(ulong)callee_acc->vt->get_data_mut( callee_acc ) ) {
     637           0 :       vm->input_mem_regions[ acc_region_idx ].haddr = (ulong)callee_acc->vt->get_data_mut( callee_acc );
     638           0 :       zero_all_mapped_spare_capacity = 1;
     639           0 :     }
     640             : 
     641           0 :     ulong prev_len = caller_acc_data_len;
     642           0 :     ulong post_len = callee_acc->vt->get_data_len( callee_acc );
     643             : 
     644             :     /* Do additional handling in the case where the data size has changed in
     645             :        the course of the callee's CPI. */
     646           0 :     if( prev_len!=post_len ) {
     647             :       /* There is an illegal data overflow if the post len is greater than the
     648             :          original data len + the max resizing limit (10KiB). Can't resize the
     649             :          account if the deprecated loader is being used */
     650           0 :       ulong max_increase = vm->is_deprecated ? 0UL : MAX_PERMITTED_DATA_INCREASE;
     651           0 :       if( FD_UNLIKELY( post_len>fd_ulong_sat_add( (ulong)original_len, max_increase ) ) ) {
     652           0 :         FD_VM_ERR_FOR_LOG_INSTR( vm, FD_EXECUTOR_INSTR_ERR_INVALID_REALLOC);
     653           0 :         return FD_EXECUTOR_INSTR_ERR_INVALID_REALLOC;
     654           0 :       }
     655             :       /* There is additonal handling in the case where the account is larger
     656             :          than it was previously, but it has still grown since it was initially
     657             :          serialized. To handle this, we need to just zero out the now unused
     658             :          space in the account resizing region. */
     659           0 :       if( post_len<prev_len && prev_len>original_len ) {
     660           0 :         ulong dirty_realloc_start = fd_ulong_max( post_len, original_len );
     661           0 :         ulong dirty_realloc_len   = fd_ulong_sat_sub( prev_len, dirty_realloc_start );
     662             :         /* We don't have to worry about multiple region writes here since we
     663             :            know the amount to zero out is located in the account's data
     664             :            resizing region. We intentionally write to the pointer despite
     665             :            loading it in because we assume that the permissions were changed
     666             :            in the callee. */
     667           0 :         uchar * dirty_region = FD_VM_MEM_HADDR_ST_WRITE_UNCHECKED( vm, caller_acc_data_vm_addr + dirty_realloc_start,
     668           0 :                                                                    alignof(uchar), dirty_realloc_len );
     669           0 :         fd_memset( dirty_region, 0, dirty_realloc_len );
     670           0 :       }
     671             : 
     672             :       /* Because the account data length changed from before to after the
     673             :          CPI we must update the fields appropriately. */
     674           0 :       ulong * ref_to_len = FD_VM_MEM_HADDR_ST( vm, caller_account->ref_to_len_in_vm.vaddr, alignof(ulong), sizeof(ulong) );
     675           0 :       *ref_to_len = post_len;
     676           0 :       ulong * serialized_len_ptr = FD_VM_MEM_HADDR_ST( vm, fd_ulong_sat_sub( caller_acc_data_vm_addr, sizeof(ulong) ), alignof(ulong), sizeof(ulong) );
     677           0 :       *serialized_len_ptr = post_len;
     678           0 :     }
     679             : 
     680             :     /* We need to zero out the end of the account data buffer if the account
     681             :        shrunk in size. This is because the bytes are accessible from within
     682             :        the VM but should be equal to zero to prevent undefined behavior. If
     683             :        prev_len > post_len, then dlen should be equal to original_len. */
     684           0 :     ulong spare_len = fd_ulong_sat_sub( fd_ulong_if( zero_all_mapped_spare_capacity, original_len, prev_len ), post_len );
     685           0 :     if( FD_UNLIKELY( spare_len ) ) {
     686           0 :       if( callee_acc->vt->get_data_len( callee_acc )>spare_len ) {
     687           0 :         memset( callee_acc->vt->get_data_mut( callee_acc ) + callee_acc->vt->get_data_len( callee_acc ) - spare_len, 0, spare_len );
     688           0 :       }
     689           0 :     }
     690             : 
     691           0 :     ulong realloc_bytes_used = fd_ulong_sat_sub( post_len, original_len );
     692           0 :     if( realloc_bytes_used && !vm->is_deprecated ) {
     693             :       /* We intentionally do a load in the case where we are writing to because
     694             :          we want to ignore the write checks. We load from the first byte of the
     695             :          resizing region */
     696           0 :       ulong resizing_idx = vm->acc_region_metas[ instr_acc_idx ].region_idx;
     697           0 :       if( vm->acc_region_metas[ instr_acc_idx ].has_data_region ) {
     698           0 :         resizing_idx++;
     699           0 :       }
     700           0 :       uchar * to_slice   = (uchar*)vm->input_mem_regions[ resizing_idx ].haddr;
     701           0 :       uchar * from_slice = callee_acc->vt->get_data_mut( callee_acc ) + original_len;
     702             : 
     703           0 :       fd_memcpy( to_slice, from_slice, realloc_bytes_used );
     704           0 :     }
     705           0 :   }
     706             : 
     707           0 :   return FD_VM_SUCCESS;
     708           0 : }
     709             : 
     710             : /* fd_vm_syscall_cpi_{rust/c} is the entrypoint for the sol_invoke_signed_{rust/c} syscalls.
     711             : 
     712             : The bulk of the high-level logic mirrors Solana's cpi_common entrypoint function at
     713             : https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/programs/bpf_loader/src/syscalls/cpi.rs#L1060
     714             : The only differences should be in the order of the error checks, which does not affect consensus.
     715             : 
     716             : 100-foot flow:
     717             : - Translate the CPI ABI structures to the FD runtime's instruction format
     718             : - Update the callee accounts with any changes made by the caller prior to this CPI instruction
     719             : - Dispatch the instruction to the FD runtime (actually making the CPI call)
     720             : - Update the caller accounts with any changes made by the callee during CPI execution
     721             : 
     722             : Paramaters:
     723             : - vm: pointer to the virtual machine handle
     724             : - instruction_va: vm address of the instruction to execute, which will be in the language-specific ABI format.
     725             : - acct_infos_va: vm address of the account infos, which will be in the language-specific ABI format.
     726             : - acct_info_cnt: number of account infos
     727             : - signers_seeds_va: vm address of the signers seeds
     728             : - signers_seeds_cnt: number of signers seeds
     729             : - _ret: pointer to the return value
     730             : */
     731             : #define VM_SYSCALL_CPI_ENTRYPOINT FD_EXPAND_THEN_CONCAT2(fd_vm_syscall_cpi_, VM_SYSCALL_CPI_ABI)
     732             : int
     733             : VM_SYSCALL_CPI_ENTRYPOINT( void *  _vm,
     734             :                            ulong   instruction_va,
     735             :                            ulong   acct_infos_va,
     736             :                            ulong   acct_info_cnt,
     737             :                            ulong   signers_seeds_va,
     738             :                            ulong   signers_seeds_cnt,
     739           0 :                            ulong * _ret ) {
     740             : 
     741           0 :   fd_vm_t * vm = (fd_vm_t *)_vm;
     742             : 
     743           0 :   FD_VM_CU_UPDATE( vm, FD_VM_INVOKE_UNITS );
     744             : 
     745             :   /* Translate instruction ********************************************/
     746             :   /* translate_instruction is the first thing that agave does
     747             :      https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/programs/bpf_loader/src/syscalls/cpi.rs#L1089 */
     748             : 
     749             :   /* Translating the CPI instruction
     750             :      https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/programs/bpf_loader/src/syscalls/cpi.rs#L420-L424 */
     751           0 :   VM_SYSCALL_CPI_INSTR_T const * cpi_instruction =
     752           0 :     FD_VM_MEM_HADDR_LD( vm, instruction_va, VM_SYSCALL_CPI_INSTR_ALIGN, VM_SYSCALL_CPI_INSTR_SIZE );
     753             : 
     754             :   /* This needs to be here for the C ABI
     755             :      https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L655
     756             :    */
     757           0 :   fd_pubkey_t const * program_id = (fd_pubkey_t *)VM_SYSCALL_CPI_INSTR_PROGRAM_ID( vm, cpi_instruction );
     758             : 
     759             :   /* Translate CPI account metas *************************************************/
     760           0 :   VM_SYSCALL_CPI_ACC_META_T const * cpi_account_metas =
     761           0 :     FD_VM_MEM_SLICE_HADDR_LD( vm, VM_SYSCALL_CPI_INSTR_ACCS_ADDR( cpi_instruction ),
     762           0 :                               VM_SYSCALL_CPI_ACC_META_ALIGN,
     763           0 :                               fd_ulong_sat_mul( VM_SYSCALL_CPI_INSTR_ACCS_LEN( cpi_instruction ), VM_SYSCALL_CPI_ACC_META_SIZE ) );
     764             : 
     765             :   /* Translate instruction data *************************************************/
     766             : 
     767           0 :   uchar const * data = FD_VM_MEM_SLICE_HADDR_LD(
     768           0 :     vm, VM_SYSCALL_CPI_INSTR_DATA_ADDR( cpi_instruction ),
     769           0 :     FD_VM_ALIGN_RUST_U8,
     770           0 :     VM_SYSCALL_CPI_INSTR_DATA_LEN( cpi_instruction ));
     771             : 
     772             : 
     773             :   /* Instruction checks ***********************************************/
     774             : 
     775           0 :   int err = fd_vm_syscall_cpi_check_instruction( vm, VM_SYSCALL_CPI_INSTR_ACCS_LEN( cpi_instruction ), VM_SYSCALL_CPI_INSTR_DATA_LEN( cpi_instruction ) );
     776           0 :   if( FD_UNLIKELY( err ) ) {
     777           0 :     FD_VM_ERR_FOR_LOG_SYSCALL( vm, err );
     778           0 :     return err;
     779           0 :   }
     780             : 
     781             :   /* Agave consumes CU in translate_instruction
     782             :      https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/programs/bpf_loader/src/syscalls/cpi.rs#L445 */
     783           0 :   if( FD_FEATURE_ACTIVE_BANK( vm->instr_ctx->txn_ctx->bank, loosen_cpi_size_restriction ) ) {
     784           0 :     FD_VM_CU_UPDATE( vm, VM_SYSCALL_CPI_INSTR_DATA_LEN( cpi_instruction ) / FD_VM_CPI_BYTES_PER_UNIT );
     785           0 :   }
     786             : 
     787             :   /* Final checks for translate_instruction
     788             :    */
     789           0 :   for( ulong i=0UL; i<VM_SYSCALL_CPI_INSTR_ACCS_LEN( cpi_instruction ); i++ ) {
     790           0 :     VM_SYSCALL_CPI_ACC_META_T const * cpi_acct_meta = &cpi_account_metas[i];
     791           0 :     if( FD_UNLIKELY( cpi_acct_meta->is_signer > 1U || cpi_acct_meta->is_writable > 1U ) ) {
     792             :       /* https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L471
     793             :          https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L698
     794             :        */
     795           0 :       FD_VM_ERR_FOR_LOG_INSTR( vm, FD_EXECUTOR_INSTR_ERR_INVALID_ARG );
     796           0 :       return FD_EXECUTOR_INSTR_ERR_INVALID_ARG;
     797           0 :     }
     798             :     /* https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L700
     799             :      */
     800           0 :     (void)VM_SYSCALL_CPI_ACC_META_PUBKEY( vm, cpi_acct_meta );
     801           0 :   }
     802             : 
     803             :   /* Derive PDA signers ************************************************/
     804           0 :   fd_pubkey_t signers[ FD_CPI_MAX_SIGNER_CNT ] = {0};
     805           0 :   fd_pubkey_t * caller_program_id = &vm->instr_ctx->txn_ctx->account_keys[ vm->instr_ctx->instr->program_id ];
     806             :   /* This is the equivalent of translate_slice in translate_signers:
     807             :      https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/programs/bpf_loader/src/syscalls/cpi.rs#L595 */
     808           0 :   if( FD_LIKELY( signers_seeds_cnt > 0UL ) ) {
     809           0 :     fd_vm_vec_t const * signers_seeds = FD_VM_MEM_SLICE_HADDR_LD( vm, signers_seeds_va, FD_VM_ALIGN_RUST_SLICE_U8_REF, fd_ulong_sat_mul( signers_seeds_cnt, FD_VM_VEC_SIZE ) );
     810             :     /* Right after translating, Agave checks against MAX_SIGNERS:
     811             :        https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/programs/bpf_loader/src/syscalls/cpi.rs#L602 */
     812           0 :     if( FD_UNLIKELY( signers_seeds_cnt > FD_CPI_MAX_SIGNER_CNT ) ) {
     813           0 :       FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_TOO_MANY_SIGNERS );
     814           0 :       return FD_VM_SYSCALL_ERR_TOO_MANY_SIGNERS;
     815           0 :     }
     816             : 
     817           0 :     for( ulong i=0UL; i<signers_seeds_cnt; i++ ) {
     818             : 
     819             :       /* This function will precompute the memory translation required and do
     820             :         some preflight checks. */
     821           0 :       void const * signer_seed_haddrs[ FD_VM_PDA_SEEDS_MAX ];
     822           0 :       ulong        signer_seed_lens  [ FD_VM_PDA_SEEDS_MAX ];
     823             : 
     824           0 :       int err = fd_vm_translate_and_check_program_address_inputs( vm,
     825           0 :                                                                   signers_seeds[i].addr,
     826           0 :                                                                   signers_seeds[i].len,
     827           0 :                                                                   0UL,
     828           0 :                                                                   signer_seed_haddrs,
     829           0 :                                                                   signer_seed_lens ,
     830           0 :                                                                   NULL,
     831           0 :                                                                   0U );
     832           0 :       if( FD_UNLIKELY( err ) ) {
     833           0 :         return err;
     834           0 :       }
     835             : 
     836           0 :       err = fd_vm_derive_pda( vm, caller_program_id, signer_seed_haddrs, signer_seed_lens, signers_seeds[i].len, NULL, &signers[i] );
     837           0 :       if( FD_UNLIKELY( err ) ) {
     838           0 :         FD_TXN_PREPARE_ERR_OVERWRITE( vm->instr_ctx->txn_ctx );
     839           0 :         FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_BAD_SEEDS );
     840           0 :         return FD_VM_SYSCALL_ERR_BAD_SEEDS;
     841           0 :       }
     842           0 :     }
     843           0 :   }
     844             : 
     845             :   /* Create the instruction to execute (in the input format the FD runtime expects) from
     846             :      the translated CPI ABI inputs. */
     847           0 :   fd_pubkey_t cpi_instr_acct_keys[ FD_INSTR_ACCT_MAX ];
     848           0 :   fd_instr_info_t * instruction_to_execute = &vm->instr_ctx->txn_ctx->cpi_instr_infos[ vm->instr_ctx->txn_ctx->cpi_instr_info_cnt++ ];
     849             : 
     850           0 :   err = VM_SYSCALL_CPI_INSTRUCTION_TO_INSTR_FUNC( vm, cpi_instruction, cpi_account_metas, program_id, data, instruction_to_execute, cpi_instr_acct_keys );
     851           0 :   if( FD_UNLIKELY( err ) ) {
     852           0 :     return err;
     853           0 :   }
     854             : 
     855             :   /* Prepare the instruction for execution in the runtime. This is required by the runtime
     856             :      before we can pass an instruction to the executor. */
     857           0 :   fd_instruction_account_t instruction_accounts[256];
     858           0 :   ulong instruction_accounts_cnt;
     859           0 :   err = fd_vm_prepare_instruction( instruction_to_execute, vm->instr_ctx, program_id, cpi_instr_acct_keys, instruction_accounts, &instruction_accounts_cnt, signers, signers_seeds_cnt );
     860             :   /* Errors are propagated in the function itself. */
     861           0 :   if( FD_UNLIKELY( err ) ) return err;
     862             : 
     863             :   /* Authorized program check *************************************************/
     864             : 
     865           0 :   if( FD_UNLIKELY( fd_vm_syscall_cpi_check_authorized_program( program_id, vm->instr_ctx->txn_ctx, data, VM_SYSCALL_CPI_INSTR_DATA_LEN( cpi_instruction ) ) ) ) {
     866             :     /* https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/programs/bpf_loader/src/syscalls/cpi.rs#L1054 */
     867           0 :     FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_PROGRAM_NOT_SUPPORTED );
     868           0 :     return FD_VM_SYSCALL_ERR_PROGRAM_NOT_SUPPORTED;
     869           0 :   }
     870             : 
     871             :   /* Translate account infos ******************************************/
     872             :   /* Direct mapping check
     873             :      https://github.com/anza-xyz/agave/blob/v2.1.0/programs/bpf_loader/src/syscalls/cpi.rs#L805-L814 */
     874           0 :   ulong acc_info_total_sz = fd_ulong_sat_mul( acct_info_cnt, VM_SYSCALL_CPI_ACC_INFO_SIZE );
     875           0 :   if( FD_UNLIKELY( vm->direct_mapping && fd_ulong_sat_add( acct_infos_va, acc_info_total_sz ) >= FD_VM_MEM_MAP_INPUT_REGION_START ) ) {
     876           0 :     FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_INVALID_POINTER );
     877           0 :     return FD_VM_SYSCALL_ERR_INVALID_POINTER;
     878           0 :   }
     879             : 
     880             :   /* This is the equivalent of translate_slice in translate_account_infos:
     881             :      https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/programs/bpf_loader/src/syscalls/cpi.rs#L816 */
     882           0 :   VM_SYSCALL_CPI_ACC_INFO_T const * acc_infos = FD_VM_MEM_SLICE_HADDR_LD( vm, acct_infos_va, VM_SYSCALL_CPI_ACC_INFO_ALIGN, acc_info_total_sz );
     883             : 
     884             :   /* Right after translating, Agave checks the number of account infos:
     885             :      https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/programs/bpf_loader/src/syscalls/cpi.rs#L822 */
     886           0 :   if( FD_FEATURE_ACTIVE_BANK( vm->instr_ctx->txn_ctx->bank, loosen_cpi_size_restriction ) ) {
     887           0 :     if( FD_UNLIKELY( acct_info_cnt > get_cpi_max_account_infos( vm->instr_ctx->txn_ctx ) ) ) {
     888           0 :       FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_MAX_INSTRUCTION_ACCOUNT_INFOS_EXCEEDED );
     889           0 :       return FD_VM_SYSCALL_ERR_MAX_INSTRUCTION_ACCOUNT_INFOS_EXCEEDED;
     890           0 :     }
     891           0 :   } else {
     892           0 :     ulong adjusted_len = fd_ulong_sat_mul( acct_info_cnt, sizeof( fd_pubkey_t ) );
     893           0 :     if ( FD_UNLIKELY( adjusted_len > FD_VM_MAX_CPI_INSTRUCTION_SIZE ) ) {
     894             :       /* "Cap the number of account_infos a caller can pass to approximate
     895             :           maximum that accounts that could be passed in an instruction" */
     896           0 :       FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_TOO_MANY_ACCOUNTS );
     897           0 :       return FD_VM_SYSCALL_ERR_TOO_MANY_ACCOUNTS;
     898           0 :     }
     899           0 :   }
     900             : 
     901           0 :   fd_pubkey_t const * acct_info_keys[ FD_CPI_MAX_ACCOUNT_INFOS ];
     902           0 :   for( ulong acct_idx = 0UL; acct_idx < acct_info_cnt; acct_idx++ ) {
     903             :     /* Translate each pubkey address specified in account_infos.
     904             :        Failed translation should lead to an access violation and
     905             :        implies that obviously bad account_info has been supplied.
     906             :        https://github.com/anza-xyz/agave/blob/v2.1.6/programs/bpf_loader/src/syscalls/cpi.rs#L833-L841 */
     907           0 :       acct_info_keys[ acct_idx ] = FD_VM_MEM_HADDR_LD( vm, acc_infos[ acct_idx ].pubkey_addr, alignof(uchar), sizeof(fd_pubkey_t) );
     908           0 :   }
     909             : 
     910             :   /* Update the callee accounts with any changes made by the caller prior to this CPI execution */
     911           0 :   fd_vm_cpi_caller_account_t caller_accounts[ 256 ];
     912           0 :   ushort callee_account_keys[256];
     913           0 :   ushort caller_accounts_to_update[256];
     914           0 :   ulong caller_accounts_to_update_len = 0;
     915           0 :   err = VM_SYSCALL_CPI_TRANSLATE_AND_UPDATE_ACCOUNTS_FUNC(
     916           0 :     vm,
     917           0 :     instruction_accounts,
     918           0 :     instruction_accounts_cnt,
     919           0 :     acct_infos_va,
     920           0 :     acct_info_keys,
     921           0 :     acc_infos,
     922           0 :     acct_info_cnt,
     923           0 :     callee_account_keys,
     924           0 :     caller_accounts_to_update,
     925           0 :     caller_accounts,
     926           0 :     &caller_accounts_to_update_len
     927           0 :   );
     928             :   /* errors are propagated in the function itself. */
     929           0 :   if( FD_UNLIKELY( err ) ) return err;
     930             : 
     931             :   /* Set the transaction compute meter to be the same as the VM's compute meter,
     932             :      so that the callee cannot use compute units that the caller has already used. */
     933           0 :   vm->instr_ctx->txn_ctx->compute_budget_details.compute_meter = vm->cu;
     934             : 
     935             :   /* Execute the CPI instruction in the runtime */
     936           0 :   int err_exec = fd_execute_instr( vm->instr_ctx->txn_ctx, instruction_to_execute );
     937           0 :   ulong instr_exec_res = (ulong)err_exec;
     938             : 
     939             :   /* Set the CU meter to the instruction context's transaction context's compute meter,
     940             :      so that the caller can't use compute units that the callee has already used. */
     941           0 :   vm->cu = vm->instr_ctx->txn_ctx->compute_budget_details.compute_meter;
     942             : 
     943           0 :   *_ret = instr_exec_res;
     944             : 
     945             :   /* Errors are propagated in fd_execute_instr. */
     946           0 :   if( FD_UNLIKELY( err_exec ) ) return err_exec;
     947             : 
     948             :   /* https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/programs/bpf_loader/src/syscalls/cpi.rs#L1128-L1145 */
     949             :   /* Update all account permissions before updating the account data updates.
     950             :      We have inlined the anza function update_caller_account_perms here.
     951             :      TODO: consider factoring this out */
     952           0 :   if( vm->direct_mapping ) {
     953           0 :     for( ulong i=0UL; i<caller_accounts_to_update_len; i++ ) {
     954           0 :       ushort acc_instr_idx = callee_account_keys[i];
     955             : 
     956             :       /* https://github.com/firedancer-io/solana/blob/508f325e19c0fd8e16683ea047d7c1a85f127e74/programs/bpf_loader/src/syscalls/cpi.rs#L939-L943 */
     957             :       /* Anza only even attemps to update the account permissions if it is a
     958             :          "caller account". Only writable accounts are caller accounts. */
     959           0 :       if( fd_instr_acc_is_writable_idx( vm->instr_ctx->instr, acc_instr_idx ) ) {
     960             : 
     961             :         /* Borrow the callee account
     962             :            https://github.com/anza-xyz/agave/blob/v2.1.14/programs/bpf_loader/src/syscalls/cpi.rs#L1154-L1155 */
     963           0 :         fd_guarded_borrowed_account_t callee_acc;
     964           0 :         FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( vm->instr_ctx, acc_instr_idx, &callee_acc );
     965             : 
     966             :         /* https://github.com/anza-xyz/agave/blob/v2.1.14/programs/bpf_loader/src/syscalls/cpi.rs#L1298 */
     967           0 :         uchar is_writable = !!fd_borrowed_account_can_data_be_changed( &callee_acc, &err );
     968             :         /* Lookup memory regions for the account data and the realloc region. */
     969           0 :         ulong data_region_idx    = vm->acc_region_metas[ acc_instr_idx ].has_data_region ? vm->acc_region_metas[ acc_instr_idx ].region_idx : 0;
     970           0 :         ulong realloc_region_idx = vm->acc_region_metas[ acc_instr_idx ].has_resizing_region ? vm->acc_region_metas[ acc_instr_idx ].region_idx : 0;
     971           0 :         if( data_region_idx && realloc_region_idx ) {
     972           0 :           realloc_region_idx++;
     973           0 :         }
     974             : 
     975           0 :         if( data_region_idx ) {
     976           0 :           vm->input_mem_regions[ data_region_idx ].is_writable = is_writable;
     977           0 :         }
     978           0 :         if( FD_LIKELY( realloc_region_idx ) ) { /* Unless is deprecated loader */
     979           0 :           vm->input_mem_regions[ realloc_region_idx ].is_writable = is_writable;
     980           0 :         }
     981           0 :       }
     982           0 :     }
     983           0 :   }
     984             : 
     985             :   /* Update the caller accounts with any changes made by the callee during CPI execution */
     986           0 :   for( ulong i=0UL; i<caller_accounts_to_update_len; i++ ) {
     987             :     /* https://github.com/firedancer-io/solana/blob/508f325e19c0fd8e16683ea047d7c1a85f127e74/programs/bpf_loader/src/syscalls/cpi.rs#L939-L943 */
     988             :     /* We only want to update the writable accounts, because the non-writable
     989             :        caller accounts can't be changed during a CPI execution. */
     990           0 :     if( fd_instr_acc_is_writable_idx( vm->instr_ctx->instr, callee_account_keys[i] ) ) {
     991           0 :       ushort              idx_in_txn = vm->instr_ctx->instr->accounts[ callee_account_keys[i] ].index_in_transaction;
     992           0 :       fd_pubkey_t const * callee     = &vm->instr_ctx->txn_ctx->account_keys[ idx_in_txn ];
     993           0 :       err = VM_SYSCALL_CPI_UPDATE_CALLER_ACC_FUNC(vm, &acc_infos[ caller_accounts_to_update[i] ], caller_accounts + i, (uchar)callee_account_keys[i], callee);
     994           0 :       if( FD_UNLIKELY( err ) ) {
     995           0 :         return err;
     996           0 :       }
     997           0 :     }
     998           0 :   }
     999             : 
    1000           0 :   return FD_VM_SUCCESS;
    1001           0 : }
    1002             : 
    1003             : #undef VM_SYSCALL_CPI_UPDATE_CALLER_ACC_FUNC
    1004             : #undef VM_SYSCALL_CPI_FROM_ACC_INFO_FUNC
    1005             : #undef VM_SYSCALL_CPI_TRANSLATE_AND_UPDATE_ACCOUNTS_FUNC
    1006             : #undef VM_SYSCALL_CPI_INSTRUCTION_TO_INSTR_FUNC
    1007             : #undef VM_SYSCALL_CPI_FUNC

Generated by: LCOV version 1.14