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

Generated by: LCOV version 1.14