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

Generated by: LCOV version 1.14