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: 206 422 48.8 %
Date: 2025-01-08 12:08:44 Functions: 9 10 90.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             : /* fd_vm_syscall_cpi_instruction_to_instr_{c/rust} takes the translated
      18             :    CPI ABI structures (instruction and account meta list), and uses these
      19             :    to populate a fd_instr_info_t struct. This struct can then be given to the
      20             :    FD runtime for execution.
      21             :    
      22             : Parameters:
      23             : - vm: handle to the vm
      24             : - cpi_instr: instruction to execute laid out in the CPI ABI format (Rust or C)
      25             : - cpi_acc_metas: list of account metas, again in the CPI ABI format
      26             : - signers: derived signers for this CPI call
      27             : - signers_cnt: length of the signers list
      28             : - cpi_instr_data: instruction data in host address space
      29             : 
      30             : TODO: return codes/errors?
      31             : */
      32         255 : #define VM_SYSCALL_CPI_INSTRUCTION_TO_INSTR_FUNC FD_EXPAND_THEN_CONCAT2(fd_vm_syscall_cpi_instruction_to_instr_, VM_SYSCALL_CPI_ABI)
      33             : static int
      34             : VM_SYSCALL_CPI_INSTRUCTION_TO_INSTR_FUNC( fd_vm_t * vm,
      35             :                             VM_SYSCALL_CPI_INSTR_T const * cpi_instr,
      36             :                             VM_SYSCALL_CPI_ACC_META_T const * cpi_acct_metas,
      37             :                             fd_pubkey_t const * program_id,
      38             :                             uchar const * cpi_instr_data,
      39         255 :                             fd_instr_info_t * out_instr ) {
      40             :   /* fd_vm_prepare_instruction will handle the case where pubkey is missing */
      41         255 :   out_instr->program_id_pubkey = *program_id;
      42         255 :   out_instr->program_id = UCHAR_MAX;
      43             : 
      44             :   /* Find the index of the CPI instruction's program account in the transaction */
      45         255 :   fd_pubkey_t * txn_accs = vm->instr_ctx->txn_ctx->accounts;
      46        7482 :   for( ulong i=0UL; i < vm->instr_ctx->txn_ctx->accounts_cnt; i++ ) {
      47        7482 :     if( !memcmp( program_id, &txn_accs[i], sizeof( fd_pubkey_t ) ) ) {
      48         255 :       out_instr->program_id = (uchar)i;
      49         255 :       break;
      50         255 :     }
      51        7482 :   }
      52             : 
      53             :   /* Calculate summary information for the account list */
      54         255 :   ulong starting_lamports_h = 0UL;
      55         255 :   ulong starting_lamports_l = 0UL;
      56         255 :   uchar acc_idx_seen[256] = {0};
      57        1470 :   for( ulong i=0UL; i<VM_SYSCALL_CPI_INSTR_ACCS_LEN( cpi_instr ); i++ ) {
      58        1215 :     VM_SYSCALL_CPI_ACC_META_T const * cpi_acct_meta = &cpi_acct_metas[i];
      59        1215 :     uchar const * pubkey = VM_SYSCALL_CPI_ACC_META_PUBKEY( vm, cpi_acct_meta );
      60             : 
      61           0 :     int account_found = 0;
      62       20580 :     for( ulong j=0UL; j<vm->instr_ctx->txn_ctx->accounts_cnt; j++ ) {
      63       20580 :       if( !memcmp( pubkey, &txn_accs[j], sizeof(fd_pubkey_t) ) ) {
      64        1215 :         account_found = 1;
      65             :         /* TODO: error if flags are wrong */
      66        1215 :         memcpy( out_instr->acct_pubkeys[i].uc, pubkey, sizeof( fd_pubkey_t ) );
      67        1215 :         out_instr->acct_txn_idxs[i]     = (uchar)j;
      68        1215 :         out_instr->acct_flags[i]        = 0;
      69        1215 :         out_instr->borrowed_accounts[i] = &vm->instr_ctx->txn_ctx->borrowed_accounts[j];
      70        1215 :         out_instr->is_duplicate[i]      = acc_idx_seen[j];
      71             : 
      72        1215 :         if( FD_LIKELY( !acc_idx_seen[j] ) ) {
      73             :           /* This is the first time seeing this account */
      74        1212 :           acc_idx_seen[j] = 1;
      75        1212 :           if( out_instr->borrowed_accounts[i]->const_meta ) {
      76             :             /* TODO: what if this account is borrowed as writable? */
      77        1212 :             fd_uwide_inc( 
      78        1212 :               &starting_lamports_h, &starting_lamports_l,
      79        1212 :               starting_lamports_h, starting_lamports_l,
      80        1212 :               out_instr->borrowed_accounts[i]->const_meta->info.lamports );
      81        1212 :           }
      82        1212 :         }
      83             : 
      84             :         /* The parent flag(s) for is writable/signer is checked in
      85             :            fd_vm_prepare_instruction. Signer privilege is allowed iff the account
      86             :            is a signer in the caller or if it is a derived signer. */
      87        1215 :         if( VM_SYSCALL_CPI_ACC_META_IS_WRITABLE( cpi_acct_meta ) ) {
      88         783 :           out_instr->acct_flags[i] |= FD_INSTR_ACCT_FLAGS_IS_WRITABLE;
      89         783 :         }
      90             : 
      91        1215 :         if( VM_SYSCALL_CPI_ACC_META_IS_SIGNER( cpi_acct_meta ) ) {
      92         264 :           out_instr->acct_flags[i] |= FD_INSTR_ACCT_FLAGS_IS_SIGNER;
      93         264 :         }
      94             : 
      95        1215 :         break;
      96        1215 :       }
      97       20580 :     }
      98        1215 :     if( FD_UNLIKELY( !account_found ) ) {
      99           0 :       return FD_EXECUTOR_INSTR_ERR_MISSING_ACC;
     100           0 :     }
     101         162 :   }
     102             : 
     103         255 :   out_instr->data_sz = (ushort)VM_SYSCALL_CPI_INSTR_DATA_LEN( cpi_instr );
     104         255 :   out_instr->data = (uchar *)cpi_instr_data;
     105         255 :   out_instr->acct_cnt = (ushort)VM_SYSCALL_CPI_INSTR_ACCS_LEN( cpi_instr );
     106         255 :   out_instr->starting_lamports_h = starting_lamports_h;
     107         255 :   out_instr->starting_lamports_l = starting_lamports_l;
     108             : 
     109         255 :   return FD_VM_SUCCESS;
     110         255 : }
     111             : 
     112             : /* 
     113             : fd_vm_syscall_cpi_update_callee_acc_{rust/c} corresponds to solana_bpf_loader_program::syscalls::cpi::update_callee_account:
     114             : https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/programs/bpf_loader/src/syscalls/cpi.rs#L1302
     115             : 
     116             : (the copy of the account stored in the instruction context's
     117             : borrowed accounts cache)
     118             : 
     119             : This function should be called before the CPI instruction is executed. Its purpose is to 
     120             : update the callee account's view of the given account with any changes the caller may made 
     121             : to the account before the CPI instruction is executed. 
     122             : 
     123             : The callee's view of the account is the borrowed accounts cache, so to update the
     124             : callee account we look up the account in the borrowed accounts cache and update it.
     125             : 
     126             : Paramaters:
     127             : - vm: pointer to the virtual machine handle
     128             : - account_info: account info object
     129             : - callee_acc_pubkey: pubkey of the account. this is used to look up the account in the borrowed accounts cache
     130             :   (TODO: this seems redundant? we can probably remove this, as the account_info contains the pubkey)
     131             : */
     132             : #define VM_SYCALL_CPI_UPDATE_CALLEE_ACC_FUNC FD_EXPAND_THEN_CONCAT2(fd_vm_syscall_cpi_update_callee_acc_, VM_SYSCALL_CPI_ABI)
     133             : static int
     134             : VM_SYCALL_CPI_UPDATE_CALLEE_ACC_FUNC( fd_vm_t *                         vm,
     135             :                                       VM_SYSCALL_CPI_ACC_INFO_T const * account_info,
     136        1098 :                                       uchar                             instr_acc_idx ) {
     137             :   /* Consume compute units for the account data access */
     138             : 
     139             :   /* FIXME: do we also need to consume the compute units if the account is not known? */
     140             : 
     141        1098 :   VM_SYSCALL_CPI_ACC_INFO_METADATA( vm, account_info, caller_acc_data );
     142             : 
     143             :   // FIXME: should this be FD_VM_CU_MEM_UPDATE? Changing this changes the CU behaviour from main
     144        1098 :   FD_VM_CU_UPDATE( vm, caller_acc_data_len / FD_VM_CPI_BYTES_PER_UNIT );
     145             : 
     146           0 :   fd_borrowed_account_t * callee_acc = NULL;
     147        1098 :   int err = fd_instr_borrowed_account_modify_idx(vm->instr_ctx, instr_acc_idx, 0, &callee_acc);
     148        1098 :   if( FD_UNLIKELY( err ) ) {
     149             :     /* No need to do anything if the account is missing from the borrowed accounts cache */
     150           0 :     return FD_VM_SUCCESS;
     151           0 :   }
     152             : 
     153        1098 :   if( FD_UNLIKELY( !callee_acc->meta ) ) {
     154             :     /* If the account is not modifiable, we can't change it (and it can't have been changed by the callee) */
     155         171 :     return FD_VM_SUCCESS;
     156         171 :   }
     157             :   
     158             :   /* Update the lamports. TODO: This should technically be a load, but it
     159             :      doesn't matter in this case.  */
     160        2781 :   VM_SYSCALL_CPI_ACC_INFO_LAMPORTS( vm, account_info, caller_acc_lamports );
     161        2781 :   if( callee_acc->meta->info.lamports!=*caller_acc_lamports ) { 
     162           0 :     err = fd_account_set_lamports( vm->instr_ctx, instr_acc_idx, *caller_acc_lamports );
     163           0 :     if( FD_UNLIKELY( err ) ) {
     164           0 :       return err;
     165           0 :     }
     166           0 :   }
     167             : 
     168         927 :   if( !vm->direct_mapping ) {
     169             :     /* Get the account data */
     170             :     /* Update the account data, if the account data can be changed */
     171             :     /* FIXME: double-check these permissions, especially the callee_acc_idx */
     172             : 
     173             :     /* Translate and get the account data */
     174        2781 :     uchar const * caller_acc_data = FD_VM_MEM_HADDR_LD( vm, caller_acc_data_vm_addr, sizeof(uchar), caller_acc_data_len ); 
     175             : 
     176         927 :     if( fd_account_can_data_be_resized( vm->instr_ctx, callee_acc->meta, caller_acc_data_len, &err ) &&
     177         927 :         fd_account_can_data_be_changed( vm->instr_ctx, instr_acc_idx, &err ) ) {
     178             :         /* We must ignore the errors here, as they are informational and do not mean the result is invalid. */
     179             :         /* TODO: not pass informational errors like this? */
     180             : 
     181          45 :       err = fd_account_set_data_from_slice( vm->instr_ctx, instr_acc_idx, caller_acc_data, caller_acc_data_len );
     182          45 :       if( FD_UNLIKELY( err ) ) {
     183           0 :         return err;
     184           0 :       }
     185         882 :     } else if( FD_UNLIKELY( caller_acc_data_len!=callee_acc->const_meta->dlen || 
     186         882 :                             memcmp( callee_acc->const_data, caller_acc_data, caller_acc_data_len ) ) ) {
     187           0 :       return err;
     188           0 :     }
     189             : 
     190         927 :     uchar const * caller_acc_owner = FD_VM_MEM_HADDR_ST( vm, account_info->owner_addr, alignof(uchar), sizeof(fd_pubkey_t) );
     191         927 :     if( memcmp( callee_acc->meta->info.owner, caller_acc_owner, sizeof(fd_pubkey_t) ) ) {
     192           0 :       fd_memcpy( callee_acc->meta->info.owner, caller_acc_owner, sizeof(fd_pubkey_t) );
     193           0 :     }
     194             : 
     195         927 :   } else { /* Direct mapping enabled */
     196           0 :     ulong region_idx  = vm->acc_region_metas[ instr_acc_idx ].region_idx;
     197           0 :     uint original_len = vm->acc_region_metas[ instr_acc_idx ].has_data_region ? 
     198           0 :                         vm->input_mem_regions[ region_idx ].region_sz : 0U;
     199           0 :     ulong prev_len    = callee_acc->const_meta->dlen;
     200           0 :     ulong post_len    = caller_acc_data_len;
     201             : 
     202           0 :     int err;
     203           0 :     if( fd_account_can_data_be_resized( vm->instr_ctx, callee_acc->meta, post_len, &err ) &&
     204           0 :         fd_account_can_data_be_changed( vm->instr_ctx, instr_acc_idx, &err ) ) {
     205             : 
     206           0 :       ulong realloc_bytes_used = fd_ulong_sat_sub( post_len, original_len );
     207             : 
     208           0 :       if( FD_UNLIKELY( vm->is_deprecated && realloc_bytes_used ) ) {
     209           0 :         return FD_EXECUTOR_INSTR_ERR_INVALID_REALLOC;
     210           0 :       }
     211             : 
     212           0 :       err = fd_account_set_data_length( vm->instr_ctx, instr_acc_idx, post_len );
     213           0 :       if( FD_UNLIKELY( err ) ) {
     214           0 :         return err;
     215           0 :       }
     216             : 
     217             : 
     218           0 :       if( realloc_bytes_used ) {
     219             :         /* We need to get the relevant data slice. However, we know that the
     220             :            current length currently exceeds the original length for the account
     221             :            data. This means that all of the additional bytes must exist in the 
     222             :            account data resizing region. As an invariant, original_len must be
     223             :            equal to the length of the account data region. This means we can 
     224             :            smartly look up the right region and don't need to worry about 
     225             :            multiple region access.We just need to load in the bytes from 
     226             :            (original len, post_len]. */
     227           0 :         uchar const * realloc_data = FD_VM_MEM_HADDR_LD( vm, caller_acc_data_vm_addr+original_len, alignof(uchar), realloc_bytes_used );
     228             : 
     229           0 :         uchar * data = NULL;
     230           0 :         ulong   dlen = 0UL;
     231           0 :         err = fd_account_get_data_mut( vm->instr_ctx, instr_acc_idx, &data, &dlen );
     232           0 :         if( FD_UNLIKELY( err ) ) {
     233           0 :           return err;
     234           0 :         }
     235           0 :         fd_memcpy( data+original_len, realloc_data, realloc_bytes_used );
     236           0 :       }
     237             :       
     238           0 :     } else if( FD_UNLIKELY( prev_len!=post_len ) ) {
     239           0 :       return err;
     240           0 :     }
     241           0 :   }
     242             : 
     243        2781 :   uchar const * caller_acc_owner = FD_VM_MEM_HADDR_LD( vm, account_info->owner_addr, alignof(uchar), sizeof(fd_pubkey_t) );
     244         927 :   if( FD_UNLIKELY( memcmp( callee_acc->meta->info.owner, caller_acc_owner, sizeof(fd_pubkey_t) ) ) ) {
     245           0 :     err = fd_account_set_owner( vm->instr_ctx, instr_acc_idx, (fd_pubkey_t*)caller_acc_owner );
     246           0 :     if( FD_UNLIKELY( err ) ) {
     247           0 :       return err;
     248           0 :     }
     249           0 :   }
     250             : 
     251         927 :   return FD_VM_SUCCESS;
     252        2781 : }
     253             : 
     254             : /* 
     255             : fd_vm_syscall_cpi_translate_and_update_accounts_ mirrors the behaviour of 
     256             : solana_bpf_loader_program::syscalls::cpi::translate_and_update_accounts:
     257             : https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/programs/bpf_loader/src/syscalls/cpi.rs#L954-L1085
     258             : 
     259             : It translates the caller accounts to the host address space, and then calls 
     260             : fd_vm_syscall_cpi_update_callee_acc to update the callee borrowed account with any changes
     261             : the caller has made to the account during execution before this CPI call.
     262             : 
     263             : It also populates the out_callee_indices and out_caller_indices arrays:
     264             : - out_callee_indices: indices of the callee accounts in the transaction
     265             : - out_caller_indices: indices of the caller accounts in the account_infos array
     266             : 
     267             : Parameters:
     268             : - vm: pointer to the virtual machine handle
     269             : - instruction_accounts: array of instruction accounts
     270             : - instruction_accounts_cnt: length of the instruction_accounts array
     271             : - account_infos: array of account infos
     272             : - account_infos_length: length of the account_infos array
     273             : 
     274             : Populates the given out_callee_indices and out_caller_indices arrays:
     275             : - out_callee_indices: indices of the callee accounts in the transaction
     276             : - out_caller_indices: indices of the caller accounts in the account_infos array
     277             : - out_len: length of the out_callee_indices and out_caller_indices arrays
     278             : */
     279         252 : #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)
     280             : static int
     281             : VM_SYSCALL_CPI_TRANSLATE_AND_UPDATE_ACCOUNTS_FUNC( 
     282             :                               fd_vm_t *                         vm,
     283             :                               fd_instruction_account_t const *  instruction_accounts,
     284             :                               ulong const                       instruction_accounts_cnt,
     285             :                               VM_SYSCALL_CPI_ACC_INFO_T const * account_infos,
     286             :                               ulong const                       account_infos_length,
     287             :                               ulong *                           out_callee_indices,
     288             :                               ulong *                           out_caller_indices,
     289         252 :                               ulong *                           out_len ) {
     290             : 
     291        1461 :   for( ulong i=0UL; i<instruction_accounts_cnt; i++ ) {
     292        1209 :     if( i!=instruction_accounts[i].index_in_callee ) {
     293             :       /* Skip duplicate accounts */
     294           3 :       continue;
     295           3 :     }
     296             : 
     297             :     /* `fd_vm_prepare_instruction()` will always set up a valid index for `index_in_caller`, so we can access the borrowed account directly.
     298             :        A borrowed account will always have non-NULL meta (if the account doesn't exist, `fd_executor_setup_borrowed_accounts_for_txn()`
     299             :        will set its meta up) */
     300        1206 :     fd_borrowed_account_t const * acc_rec     = vm->instr_ctx->instr->borrowed_accounts[instruction_accounts[i].index_in_caller];
     301        1206 :     fd_pubkey_t const *           account_key = acc_rec->pubkey;
     302        1206 :     fd_account_meta_t const *     acc_meta    = acc_rec->const_meta;
     303             : 
     304             :     /* If the account is known and executable, we only need to consume the compute units.
     305             :        Executable accounts can't be modified, so we don't need to update the callee account. */
     306        1206 :     if( fd_account_is_executable( acc_meta ) ) {
     307             :       // FIXME: should this be FD_VM_CU_MEM_UPDATE? Changing this changes the CU behaviour from main (because of the base cost)
     308         108 :       FD_VM_CU_UPDATE( vm, acc_meta->dlen / FD_VM_CPI_BYTES_PER_UNIT );
     309           0 :       continue;
     310         108 :     }
     311             : 
     312             :     /* Find the indicies of the account in the caller and callee instructions */
     313        1098 :     uint found = 0;
     314        6744 :     for( ulong j=0; (j < account_infos_length) && !found; j++ ) {
     315             : 
     316             :       /* Look up the pubkey to see if it is the account we're looking for,
     317             :          error out if invalid address (implies bad account_infos and is also what Agave does).
     318             :          https://github.com/firedancer-io/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/programs/bpf_loader/src/syscalls/cpi.rs#L828-L832 */
     319       16938 :       fd_pubkey_t const * acct_addr = FD_VM_MEM_HADDR_LD( 
     320       16938 :         vm, account_infos[j].pubkey_addr, alignof(uchar), sizeof(fd_pubkey_t) );
     321             : 
     322        5646 :       if( memcmp( account_key->uc, acct_addr->uc, sizeof(fd_pubkey_t) ) != 0 ) {
     323        4548 :         continue;
     324        4548 :       }
     325             : 
     326             :       /* Record the indicies of this account */
     327        1098 :       ulong index_in_caller = instruction_accounts[i].index_in_caller;
     328        1098 :       if (instruction_accounts[i].is_writable) {
     329         768 :         out_callee_indices[*out_len] = index_in_caller;
     330         768 :         out_caller_indices[*out_len] = j;
     331         768 :         (*out_len)++;
     332         768 :       }
     333        1098 :       found = 1;
     334             : 
     335        1098 :       if ( vm->direct_mapping ) {
     336             :         /* Check that the account info pointers given by the user correspond to the correct locations
     337             :            in the serialized account metadata
     338             :            
     339             :            https://github.com/anza-xyz/agave/blob/v2.1.7/programs/bpf_loader/src/syscalls/cpi.rs#L116 */
     340           0 :         fd_vm_acc_region_meta_t * acc_region_meta = &vm->acc_region_metas[index_in_caller];
     341           0 :         ulong expected_pubkey_vaddr = serialized_pubkey_vaddr( vm, acc_region_meta );
     342           0 :         if( FD_UNLIKELY( account_infos[j].pubkey_addr!=expected_pubkey_vaddr )) {
     343             :           /* Max msg_sz: 40 + 18 + 18 = 76 < 127 => we can use printf */
     344           0 :           fd_log_collector_printf_dangerous_max_127( vm->instr_ctx,
     345           0 :             "Invalid account info pointer `key': %#lx != %#lx", account_infos[j].pubkey_addr, expected_pubkey_vaddr );
     346           0 :           return FD_VM_SYSCALL_ERR_INVALID_POINTER;
     347           0 :         }
     348             : 
     349             :         /* https://github.com/anza-xyz/agave/blob/v2.1.7/programs/bpf_loader/src/syscalls/cpi.rs#L122 */
     350           0 :         ulong expected_owner_vaddr = serialized_owner_vaddr( vm, acc_region_meta );
     351           0 :         if( FD_UNLIKELY( account_infos[j].owner_addr!=expected_owner_vaddr )) {
     352             :           /* Max msg_sz: 42 + 18 + 18 = 78 < 127 => we can use printf */
     353           0 :           fd_log_collector_printf_dangerous_max_127( vm->instr_ctx,
     354           0 :             "Invalid account info pointer `owner': %#lx != %#lx", account_infos[j].owner_addr, expected_owner_vaddr );
     355           0 :           return FD_VM_SYSCALL_ERR_INVALID_POINTER;
     356           0 :         }
     357             : 
     358             :         /* Check that the account's lamports Rc<RefCell<T>> is not stored in the account. Because a refcell is
     359             :            only present if the Rust SDK is used, we only need to check this for the Rust SDK.
     360             : 
     361             :          https://github.com/anza-xyz/agave/blob/v2.1.7/programs/bpf_loader/src/syscalls/cpi.rs#L140 */
     362             :         #ifdef VM_SYSCALL_CPI_ACC_INFO_LAMPORTS_RC_REFCELL_VADDR
     363           0 :         VM_SYSCALL_CPI_ACC_INFO_LAMPORTS_RC_REFCELL_VADDR( vm, (account_infos + j), lamports_rc_vaddr )
     364           0 :         if ( FD_UNLIKELY( lamports_rc_vaddr >= FD_VM_MEM_MAP_INPUT_REGION_START ) ) {
     365           0 :           return FD_VM_SYSCALL_ERR_INVALID_POINTER;
     366           0 :         }
     367           0 :         #endif
     368             : 
     369             :         /* https://github.com/anza-xyz/agave/blob/v2.1.7/programs/bpf_loader/src/syscalls/cpi.rs#L144 */
     370           0 :         VM_SYSCALL_CPI_ACC_INFO_LAMPORTS_VADDR( vm, (account_infos + j), lamports_vaddr )
     371           0 :         ulong expected_lamports_vaddr = serialized_lamports_vaddr( vm, acc_region_meta );
     372           0 :         if( FD_UNLIKELY( lamports_vaddr!=expected_lamports_vaddr )) {
     373             :           /* Max msg_sz: 45 + 18 + 18 = 81 < 127 => we can use printf */
     374           0 :           fd_log_collector_printf_dangerous_max_127( vm->instr_ctx,
     375           0 :             "Invalid account info pointer `lamports': %#lx != %#lx", lamports_vaddr, expected_lamports_vaddr );
     376           0 :           return FD_VM_SYSCALL_ERR_INVALID_POINTER;
     377           0 :         }
     378             : 
     379             :         /* Check that the account's data Rc<RefCell<T>> is not stored in the account. Because a refcell is
     380             :            only present if the Rust SDK is used, we only need to check this for the Rust SDK.
     381             : 
     382             :          https://github.com/anza-xyz/agave/blob/v2.1.7/programs/bpf_loader/src/syscalls/cpi.rs#L161 */
     383             :         #ifdef VM_SYSCALL_CPI_ACC_INFO_DATA_RC_REFCELL_VADDR
     384           0 :         VM_SYSCALL_CPI_ACC_INFO_DATA_RC_REFCELL_VADDR( vm, (account_infos + j), data_rc_vaddr )
     385           0 :         if( FD_UNLIKELY( data_rc_vaddr >= FD_VM_MEM_MAP_INPUT_REGION_START ) ) {
     386           0 :           return FD_VM_SYSCALL_ERR_INVALID_POINTER;
     387           0 :         }
     388           0 :         #endif
     389             : 
     390             :         /* https://github.com/anza-xyz/agave/blob/v2.1.7/programs/bpf_loader/src/syscalls/cpi.rs#L172 */
     391           0 :         ulong expected_data_region_vaddr = FD_VM_MEM_MAP_INPUT_REGION_START +
     392           0 :           vm->input_mem_regions[acc_region_meta->region_idx].vaddr_offset;
     393           0 :         VM_SYSCALL_CPI_ACC_INFO_DATA_VADDR( vm, (account_infos + j), data_vaddr )
     394           0 :         if( FD_UNLIKELY( data_vaddr!=expected_data_region_vaddr )) {
     395             :           /* Max msg_sz: 41 + 18 + 18 = 77 < 127 => we can use printf */
     396           0 :           fd_log_collector_printf_dangerous_max_127( vm->instr_ctx,
     397           0 :             "Invalid account info pointer `data': %#lx != %#lx", data_vaddr, expected_data_region_vaddr );
     398           0 :           return FD_VM_SYSCALL_ERR_INVALID_POINTER;
     399           0 :         }
     400           0 :       }
     401             : 
     402             :       /* Update the callee account to reflect any changes the caller has made */
     403        1098 :       if( FD_UNLIKELY( VM_SYCALL_CPI_UPDATE_CALLEE_ACC_FUNC(vm, &account_infos[j], (uchar)index_in_caller ) ) ) {
     404           0 :         return 1001;
     405           0 :       }
     406        1098 :     }
     407             : 
     408        1098 :     if( !found ) {
     409             :       /* TODO: magic number */
     410           0 :       return 1002;
     411           0 :     }
     412        1098 :   }
     413             :   
     414         252 :   return FD_VM_SUCCESS;
     415         252 : }
     416             : 
     417             : /* fd_vm_cpi_update_caller_acc_{rust/c} mirrors the behaviour of 
     418             : solana_bpf_loader_program::syscalls::cpi::update_caller_account:
     419             : https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/programs/bpf_loader/src/syscalls/cpi.rs#L1291
     420             : 
     421             : This method should be called after a CPI instruction execution has
     422             : returned. It updates the given caller account info with any changes the callee
     423             : has made to this account during execution, so that those changes are
     424             : reflected in the rest of the caller's execution.
     425             : 
     426             : Those changes will be in the instructions borrowed accounts cache.
     427             : 
     428             : Paramaters:
     429             : - vm: handle to the vm
     430             : - caller_acc_info: caller account info object, which should be updated
     431             : - pubkey: pubkey of the account
     432             : 
     433             : TODO: error codes
     434             : */
     435         768 : #define VM_SYSCALL_CPI_UPDATE_CALLER_ACC_FUNC FD_EXPAND_THEN_CONCAT2(fd_vm_cpi_update_caller_acc_, VM_SYSCALL_CPI_ABI)
     436             : static int
     437             : VM_SYSCALL_CPI_UPDATE_CALLER_ACC_FUNC( fd_vm_t *                   vm,
     438             :                                        VM_SYSCALL_CPI_ACC_INFO_T * caller_acc_info,
     439             :                                        uchar                       instr_acc_idx,
     440         768 :                                        fd_pubkey_t const *         pubkey ) {
     441             : 
     442         768 :   if( !vm->direct_mapping ) {
     443             :     /* Look up the borrowed account from the instruction context, which will contain
     444             :       the callee's changes. */
     445         768 :     fd_borrowed_account_t * callee_acc_rec = NULL;
     446         768 :     int err = fd_instr_borrowed_account_view( vm->instr_ctx, pubkey, &callee_acc_rec );
     447         768 :     if( FD_UNLIKELY( err && ( err != FD_ACC_MGR_ERR_UNKNOWN_ACCOUNT ) ) ) {
     448           0 :       return 1;
     449           0 :     }
     450             :     
     451             :     /* Update the caller account lamports with the value from the callee */
     452        2304 :     VM_SYSCALL_CPI_ACC_INFO_LAMPORTS( vm, caller_acc_info, caller_acc_lamports );
     453        2304 :     *caller_acc_lamports = callee_acc_rec->const_meta->info.lamports;
     454             : 
     455             :     /* Update the caller account owner with the value from the callee */
     456        2304 :     uchar const * updated_owner = callee_acc_rec->const_meta->info.owner;
     457        2304 :     uchar * caller_acc_owner = FD_VM_MEM_HADDR_ST( vm, caller_acc_info->owner_addr, alignof(uchar), sizeof(fd_pubkey_t) );
     458         768 :     if( updated_owner ) fd_memcpy( caller_acc_owner, updated_owner, sizeof(fd_pubkey_t) );
     459           0 :     else                fd_memset( caller_acc_owner, 0,             sizeof(fd_pubkey_t) );
     460             : 
     461             :     /* Update the caller account data with the value from the callee */
     462        1536 :     VM_SYSCALL_CPI_ACC_INFO_DATA( vm, caller_acc_info, caller_acc_data );
     463             : 
     464        1536 :     ulong const updated_data_len = callee_acc_rec->const_meta->dlen;
     465        1536 :     if( !updated_data_len ) fd_memset( (void*)caller_acc_data, 0, caller_acc_data_len ); 
     466             : 
     467        1536 :     if( caller_acc_data_len != updated_data_len ) {    
     468             :       /* FIXME: missing MAX_PERMITTED_DATA_INCREASE check from solana
     469             :         https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/programs/bpf_loader/src/syscalls/cpi.rs#L1342 */
     470             : 
     471             :       /* FIXME: do we need to zero the memory that was previously used, if the new data_len is smaller?
     472             :       https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/programs/bpf_loader/src/syscalls/cpi.rs#L1361
     473             :         I don't think we do but need to double-check. */
     474             : 
     475           0 :       VM_SYSCALL_CPI_SET_ACC_INFO_DATA_LEN( vm, caller_acc_info, caller_acc_data, updated_data_len );
     476             : 
     477             :       /* Update the serialized len field 
     478             :         https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/programs/bpf_loader/src/syscalls/cpi.rs#L1437 */
     479           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) );
     480           0 :       *caller_len = updated_data_len;
     481             : 
     482             :       /* FIXME return instruction error account data size too small in the same scenarios solana does
     483             :         https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/programs/bpf_loader/src/syscalls/cpi.rs#L1534 */
     484           0 :     }
     485             : 
     486         768 :     fd_memcpy( (void*)caller_acc_data, callee_acc_rec->const_data, updated_data_len );
     487         768 :   } else { /* Direct mapping enabled */
     488             : 
     489             :     /* Look up the borrowed account from the instruction context, which will 
     490             :        contain the callee's changes. */
     491           0 :     fd_borrowed_account_t * callee_acc_rec = NULL;
     492           0 :     int err = fd_instr_borrowed_account_view( vm->instr_ctx, pubkey, &callee_acc_rec );
     493           0 :     if( FD_UNLIKELY( err && err!=FD_ACC_MGR_ERR_UNKNOWN_ACCOUNT ) ) {
     494           0 :       return 1;
     495           0 :     }
     496             : 
     497             :     /* Update the caller account lamports with the value from the callee */
     498           0 :     VM_SYSCALL_CPI_ACC_INFO_LAMPORTS( vm, caller_acc_info, caller_acc_lamports );
     499           0 :     *caller_acc_lamports = callee_acc_rec->const_meta->info.lamports;
     500             : 
     501             :     /* Update the caller account owner with the value from the callee */
     502           0 :     uchar const * updated_owner = callee_acc_rec->const_meta->info.owner;
     503           0 :     uchar * caller_acc_owner    = (uchar*)FD_VM_MEM_HADDR_ST( vm, caller_acc_info->owner_addr, alignof(uchar), sizeof(fd_pubkey_t) );
     504           0 :     if( updated_owner ) { 
     505           0 :       fd_memcpy( caller_acc_owner, updated_owner, sizeof(fd_pubkey_t) );
     506           0 :     } else { 
     507           0 :       fd_memset( caller_acc_owner, 0,             sizeof(fd_pubkey_t) );
     508           0 :     }
     509             : 
     510             :     /* Make sure that the capacity of the borrowed account is sized up in case
     511             :        it was shrunk in the CPI. It needs to be sized up in order to fit within
     512             :        the originally delinated regions when the account data was serialized.
     513             :        https://github.com/anza-xyz/agave/blob/36323b6dcd3e29e4d6fe6d73d716a3f33927148b/programs/bpf_loader/src/syscalls/cpi.rs#L1311 */
     514           0 :     VM_SYSCALL_CPI_ACC_INFO_METADATA( vm, caller_acc_info, caller_acc_data );
     515           0 :     ulong region_idx = vm->acc_region_metas[ instr_acc_idx ].region_idx;
     516           0 :     uint original_len = vm->acc_region_metas[ instr_acc_idx ].has_data_region ? 
     517           0 :                         vm->input_mem_regions[ region_idx ].region_sz : 0U;
     518             : 
     519           0 :     uchar zero_all_mapped_spare_capacity = 0;
     520             :     /* This case can only be triggered if the original length is more than 0 */
     521           0 :     if( callee_acc_rec->const_meta->dlen < original_len ) {
     522           0 :       ulong new_len = callee_acc_rec->const_meta->dlen;
     523             :       /* Allocate into the buffer to make sure that the original data len
     524             :          is still valid but don't change the dlen. Zero out the rest of the
     525             :          memory which is not used. */
     526           0 :       err = fd_instr_borrowed_account_modify( vm->instr_ctx, pubkey, original_len, &callee_acc_rec );
     527           0 :       if( FD_UNLIKELY( err!=FD_ACC_MGR_SUCCESS ) ) {
     528           0 :         return 1;
     529           0 :       }
     530           0 :       callee_acc_rec->meta->dlen = new_len;
     531           0 :       zero_all_mapped_spare_capacity = 1;
     532           0 :     }
     533             : 
     534             :     /* Update the account data region if an account data region exists. We
     535             :        know that one exists iff the original len was non-zero. */
     536           0 :     ulong acc_region_idx = vm->acc_region_metas[instr_acc_idx].region_idx;
     537           0 :     if( original_len && vm->input_mem_regions[ acc_region_idx ].haddr!=(ulong)callee_acc_rec->data ) {
     538           0 :       vm->input_mem_regions[ acc_region_idx ].haddr = (ulong)callee_acc_rec->data;
     539           0 :       zero_all_mapped_spare_capacity = 1;
     540           0 :     }
     541             : 
     542           0 :     ulong prev_len = caller_acc_data_len;
     543           0 :     ulong post_len = callee_acc_rec->const_meta->dlen;
     544             : 
     545             :     /* Do additional handling in the case where the data size has changed in
     546             :        the course of the callee's CPI. */
     547           0 :     if( prev_len!=post_len ) {
     548             :       /* There is an illegal data overflow if the post len is greater than the
     549             :          original data len + the max resizing limit (10KiB). Can't resize the
     550             :          account if the deprecated loader is being used */
     551           0 :       ulong max_increase = vm->is_deprecated ? 0UL : 10240UL;
     552           0 :       if( FD_UNLIKELY( post_len>fd_ulong_sat_add( (ulong)original_len, max_increase ) ) ) {
     553           0 :         return FD_EXECUTOR_INSTR_ERR_INVALID_REALLOC;
     554           0 :       }
     555             :       /* There is additonal handling in the case where the account is larger
     556             :          than it was previously, but it has still grown since it was initially
     557             :          serialized. To handle this, we need to just zero out the now unused
     558             :          space in the account resizing region. */
     559           0 :       if( post_len<prev_len && prev_len>original_len ) {
     560           0 :         ulong dirty_realloc_start = fd_ulong_max( post_len, original_len );
     561           0 :         ulong dirty_realloc_len   = fd_ulong_sat_sub( prev_len, dirty_realloc_start );
     562             :         /* We don't have to worry about multiple region writes here since we
     563             :            know the amount to zero out is located in the account's data
     564             :            resizing region. We intentionally write to the pointer despite
     565             :            loading it in because we assume that the permissions were changed
     566             :            in the callee. */
     567           0 :         uchar * dirty_region = FD_VM_MEM_HADDR_ST_WRITE_UNCHECKED( vm, caller_acc_data_vm_addr + dirty_realloc_start,
     568           0 :                                                                    alignof(uchar), dirty_realloc_len );
     569           0 :         fd_memset( dirty_region, 0, dirty_realloc_len );
     570           0 :       }
     571             : 
     572             :       /* Because the account data length changed from before to after the
     573             :          CPI we must update the fields appropriately. */
     574           0 :       VM_SYSCALL_CPI_SET_ACC_INFO_DATA_LEN( vm, caller_acc_info, caller_acc_data, post_len );
     575           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) );
     576           0 :       *caller_len = post_len;
     577           0 :     }
     578             : 
     579             :     /* We need to zero out the end of the account data buffer if the account
     580             :        shrunk in size. This is because the bytes are accessible from within
     581             :        the VM but should be equal to zero to prevent undefined behavior. If
     582             :        prev_len > post_len, then dlen should be equal to original_len. */
     583           0 :     ulong spare_len = fd_ulong_sat_sub( fd_ulong_if( zero_all_mapped_spare_capacity, original_len, prev_len ), post_len );
     584           0 :     if( FD_UNLIKELY( spare_len ) ) {
     585           0 :       if( callee_acc_rec->const_meta->dlen>spare_len ) {
     586           0 :         memset( callee_acc_rec->data+callee_acc_rec->const_meta->dlen-spare_len, 0, spare_len );
     587           0 :       }
     588           0 :     }
     589             : 
     590           0 :     ulong realloc_bytes_used = fd_ulong_sat_sub( post_len, original_len );
     591           0 :     if( realloc_bytes_used && !vm->is_deprecated ) {
     592             :       /* We intentionally do a load in the case where we are writing to because
     593             :          we want to ignore the write checks. We load from the first byte of the
     594             :          resizing region */
     595           0 :       ulong resizing_idx = vm->acc_region_metas[ instr_acc_idx ].region_idx;
     596           0 :       if( vm->acc_region_metas[ instr_acc_idx ].has_data_region ) {
     597           0 :         resizing_idx++;
     598           0 :       }
     599           0 :       uchar * to_slice   = (uchar*)vm->input_mem_regions[ resizing_idx ].haddr;
     600           0 :       uchar * from_slice = callee_acc_rec->data + original_len;
     601             : 
     602           0 :       fd_memcpy( to_slice, from_slice, realloc_bytes_used );
     603           0 :     }
     604           0 :   }
     605             : 
     606         768 :   return FD_VM_SUCCESS;
     607         768 : }
     608             : 
     609             : /* fd_vm_syscall_cpi_{rust/c} is the entrypoint for the sol_invoke_signed_{rust/c} syscalls.
     610             : 
     611             : The bulk of the high-level logic mirrors Solana's cpi_common entrypoint function at
     612             : https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/programs/bpf_loader/src/syscalls/cpi.rs#L1060
     613             : The only differences should be in the order of the error checks, which does not affect consensus.
     614             : 
     615             : 100-foot flow:
     616             : - Translate the CPI ABI structures to the FD runtime's instruction format
     617             : - Update the callee accounts with any changes made by the caller prior to this CPI instruction
     618             : - Dispatch the instruction to the FD runtime (actually making the CPI call)
     619             : - Update the caller accounts with any changes made by the callee during CPI execution
     620             : 
     621             : Paramaters:
     622             : - vm: pointer to the virtual machine handle
     623             : - instruction_va: vm address of the instruction to execute, which will be in the language-specific ABI format.
     624             : - acct_infos_va: vm address of the account infos, which will be in the language-specific ABI format.
     625             : - acct_info_cnt: number of account infos
     626             : - signers_seeds_va: vm address of the signers seeds
     627             : - signers_seeds_cnt: number of signers seeds
     628             : - _ret: pointer to the return value
     629             : */
     630             : #define VM_SYSCALL_CPI_ENTRYPOINT FD_EXPAND_THEN_CONCAT2(fd_vm_syscall_cpi_, VM_SYSCALL_CPI_ABI)
     631             : int
     632             : VM_SYSCALL_CPI_ENTRYPOINT( void *  _vm,
     633             :                            ulong   instruction_va,
     634             :                            ulong   acct_infos_va,
     635             :                            ulong   acct_info_cnt,
     636             :                            ulong   signers_seeds_va,
     637             :                            ulong   signers_seeds_cnt,
     638         258 :                            ulong * _ret ) {
     639             : 
     640         258 :   fd_vm_t * vm = (fd_vm_t *)_vm;
     641             : 
     642         258 :   FD_VM_CU_UPDATE( vm, FD_VM_INVOKE_UNITS );
     643             : 
     644             :   /* Translate instruction ********************************************/
     645             :   /* translate_instruction is the first thing that agave does
     646             :      https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/programs/bpf_loader/src/syscalls/cpi.rs#L1089 */
     647             : 
     648             :   /* Translating the CPI instruction
     649             :      https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/programs/bpf_loader/src/syscalls/cpi.rs#L420-L424 */
     650         255 :   VM_SYSCALL_CPI_INSTR_T const * cpi_instruction =
     651         765 :     FD_VM_MEM_HADDR_LD( vm, instruction_va, VM_SYSCALL_CPI_INSTR_ALIGN, VM_SYSCALL_CPI_INSTR_SIZE );
     652             : 
     653             :   /* Translate the program ID */
     654         255 :   fd_pubkey_t const * program_id = (fd_pubkey_t *)VM_SYSCALL_CPI_INSTR_PROGRAM_ID( vm, cpi_instruction );
     655             : 
     656             :   /* Translate CPI account metas *************************************************/
     657         255 :   VM_SYSCALL_CPI_ACC_META_T const * cpi_account_metas =
     658         510 :     FD_VM_MEM_SLICE_HADDR_LD( vm, VM_SYSCALL_CPI_INSTR_ACCS_ADDR( cpi_instruction ),
     659         510 :                               VM_SYSCALL_CPI_ACC_META_ALIGN,
     660         510 :                               fd_ulong_sat_mul( VM_SYSCALL_CPI_INSTR_ACCS_LEN( cpi_instruction ), VM_SYSCALL_CPI_ACC_META_SIZE ) );
     661             : 
     662             :   /* Agave consumes CU in translate_instruction
     663             :      https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/programs/bpf_loader/src/syscalls/cpi.rs#L445 */
     664         255 :   if( FD_FEATURE_ACTIVE( vm->instr_ctx->slot_ctx, loosen_cpi_size_restriction ) ) {
     665           0 :     FD_VM_CU_UPDATE( vm, VM_SYSCALL_CPI_INSTR_DATA_LEN( cpi_instruction ) / FD_VM_CPI_BYTES_PER_UNIT );
     666           0 :   }
     667             : 
     668             :   /* Derive PDA signers ************************************************/
     669         255 :   fd_pubkey_t signers[ FD_CPI_MAX_SIGNER_CNT ] = {0};
     670         255 :   fd_pubkey_t * caller_program_id = &vm->instr_ctx->txn_ctx->accounts[ vm->instr_ctx->instr->program_id ];
     671             :   /* This is the equivalent of translate_slice in translate_signers:
     672             :      https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/programs/bpf_loader/src/syscalls/cpi.rs#L595 */
     673         510 :   fd_vm_vec_t const * signers_seeds = FD_VM_MEM_SLICE_HADDR_LD( vm, signers_seeds_va, FD_VM_VEC_ALIGN, fd_ulong_sat_mul( signers_seeds_cnt, FD_VM_VEC_SIZE ) );
     674             :   /* Right after translating, Agave checks against MAX_SIGNERS:
     675             :      https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/programs/bpf_loader/src/syscalls/cpi.rs#L602 */
     676         255 :   if( FD_UNLIKELY( signers_seeds_cnt > FD_CPI_MAX_SIGNER_CNT ) ) {
     677           0 :     FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_TOO_MANY_SIGNERS );
     678           0 :     return FD_VM_SYSCALL_ERR_TOO_MANY_SIGNERS;
     679           0 :   }
     680             : 
     681         378 :   for( ulong i=0UL; i<signers_seeds_cnt; i++ ) {
     682             : 
     683             :     /* This function will precompute the memory translation required and do
     684             :        some preflight checks. */
     685         123 :     void const * signer_seed_haddrs[ FD_VM_PDA_SEEDS_MAX ];
     686         123 :     ulong        signer_seed_lens  [ FD_VM_PDA_SEEDS_MAX ];
     687             : 
     688         123 :     int err = fd_vm_translate_and_check_program_address_inputs( vm, 
     689         123 :                                                                 signers_seeds[i].addr, 
     690         123 :                                                                 signers_seeds[i].len,
     691         123 :                                                                 0UL,
     692         123 :                                                                 signer_seed_haddrs,
     693         123 :                                                                 signer_seed_lens ,
     694         123 :                                                                 NULL );
     695         123 :     if( FD_UNLIKELY( err ) ) {
     696           0 :       return err;
     697           0 :     }
     698             : 
     699         123 :     err = fd_vm_derive_pda( vm, caller_program_id, signer_seed_haddrs, signer_seed_lens, signers_seeds[i].len, NULL, &signers[i] );
     700         123 :     if( FD_UNLIKELY( err ) ) {
     701           0 :       FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_BAD_SEEDS );
     702           0 :       return FD_VM_SYSCALL_ERR_BAD_SEEDS;
     703           0 :     }
     704         123 :   }
     705             : 
     706             :   /* Translate instruction data *************************************************/
     707             : 
     708         510 :   uchar const * data = FD_VM_MEM_SLICE_HADDR_LD( 
     709         510 :     vm, VM_SYSCALL_CPI_INSTR_DATA_ADDR( cpi_instruction ),
     710         510 :     FD_VM_ALIGN_RUST_U8,
     711         510 :     VM_SYSCALL_CPI_INSTR_DATA_LEN( cpi_instruction ));
     712             : 
     713             :   /* Authorized program check *************************************************/
     714             : 
     715         255 :   if( FD_UNLIKELY( fd_vm_syscall_cpi_check_authorized_program( program_id, vm->instr_ctx->slot_ctx, data, VM_SYSCALL_CPI_INSTR_DATA_LEN( cpi_instruction ) ) ) ) {
     716             :     /* https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/programs/bpf_loader/src/syscalls/cpi.rs#L1054 */
     717           0 :     FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_PROGRAM_NOT_SUPPORTED );
     718           0 :     return FD_VM_SYSCALL_ERR_PROGRAM_NOT_SUPPORTED;
     719           0 :   }
     720             : 
     721             :   /* Instruction checks ***********************************************/
     722             : 
     723         255 :   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 ) );
     724         255 :   if( FD_UNLIKELY( err ) ) {
     725           0 :     FD_VM_ERR_FOR_LOG_SYSCALL( vm, err );
     726           0 :     return err;
     727           0 :   }
     728             : 
     729             :   /* Create the instruction to execute (in the input format the FD runtime expects) from
     730             :      the translated CPI ABI inputs. */
     731         255 :   fd_instr_info_t * instruction_to_execute = &vm->instr_ctx->txn_ctx->instr_infos[ vm->instr_ctx->txn_ctx->instr_info_cnt ];
     732             : 
     733         255 :   vm->instr_ctx->txn_ctx->instr_info_cnt++;
     734         255 :   if( FD_UNLIKELY( vm->instr_ctx->txn_ctx->instr_info_cnt>FD_MAX_INSTRUCTION_TRACE_LENGTH ) ) {
     735           0 :      return FD_EXECUTOR_INSTR_ERR_MAX_INSN_TRACE_LENS_EXCEEDED;;
     736           0 :   }
     737             : 
     738         255 :   err = VM_SYSCALL_CPI_INSTRUCTION_TO_INSTR_FUNC( vm, cpi_instruction, cpi_account_metas, program_id, data, instruction_to_execute );
     739         255 :   if( FD_UNLIKELY( err ) ) return err;
     740             : 
     741             :   /* Prepare the instruction for execution in the runtime. This is required by the runtime
     742             :      before we can pass an instruction to the executor. */
     743         255 :   fd_instruction_account_t instruction_accounts[256];
     744         255 :   ulong instruction_accounts_cnt;
     745         255 :   err = fd_vm_prepare_instruction( vm->instr_ctx->instr, instruction_to_execute, vm->instr_ctx, instruction_accounts, &instruction_accounts_cnt, signers, signers_seeds_cnt );
     746         255 :   if( FD_UNLIKELY( err ) ) {
     747             :     /* We should propogate the instruction error from fd_vm_prepare_instruction. */
     748           3 :     FD_VM_ERR_FOR_LOG_INSTR( vm, err );
     749           3 :     return err;
     750           3 :   }
     751             : 
     752             :   /* Translate account infos ******************************************/
     753             :   /* Direct mapping check 
     754             :      https://github.com/anza-xyz/agave/blob/v2.1.0/programs/bpf_loader/src/syscalls/cpi.rs#L805-L814 */
     755         252 :   ulong acc_info_total_sz = fd_ulong_sat_mul( acct_info_cnt, VM_SYSCALL_CPI_ACC_INFO_SIZE );
     756         252 :   if( FD_UNLIKELY( vm->direct_mapping && fd_ulong_sat_add( acct_infos_va, acc_info_total_sz ) >= FD_VM_MEM_MAP_INPUT_REGION_START ) ) {
     757           0 :     FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_INVALID_POINTER );
     758           0 :     return FD_VM_SYSCALL_ERR_INVALID_POINTER;
     759           0 :   }
     760             : 
     761             :   /* This is the equivalent of translate_slice in translate_account_infos:
     762             :      https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/programs/bpf_loader/src/syscalls/cpi.rs#L816 */
     763         504 :   VM_SYSCALL_CPI_ACC_INFO_T * acc_infos = FD_VM_MEM_SLICE_HADDR_ST( vm, acct_infos_va, VM_SYSCALL_CPI_ACC_INFO_ALIGN, acc_info_total_sz );
     764             : 
     765             :   /* Right after translating, Agave checks the number of account infos:
     766             :      https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/programs/bpf_loader/src/syscalls/cpi.rs#L822 */
     767         252 :   if( FD_FEATURE_ACTIVE( vm->instr_ctx->slot_ctx, loosen_cpi_size_restriction ) ) {
     768           0 :     if( FD_UNLIKELY( acct_info_cnt > get_cpi_max_account_infos( vm->instr_ctx->slot_ctx ) ) ) {
     769           0 :       FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_MAX_INSTRUCTION_ACCOUNT_INFOS_EXCEEDED );
     770           0 :       return FD_VM_SYSCALL_ERR_MAX_INSTRUCTION_ACCOUNT_INFOS_EXCEEDED;
     771           0 :     }
     772         252 :   } else {
     773         252 :     ulong adjusted_len = fd_ulong_sat_mul( acct_info_cnt, sizeof( fd_pubkey_t ) );
     774         252 :     if ( FD_UNLIKELY( adjusted_len > FD_VM_MAX_CPI_INSTRUCTION_SIZE ) ) {
     775             :       /* "Cap the number of account_infos a caller can pass to approximate
     776             :           maximum that accounts that could be passed in an instruction" */
     777           0 :       FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_TOO_MANY_ACCOUNTS );
     778           0 :       return FD_VM_SYSCALL_ERR_TOO_MANY_ACCOUNTS;
     779           0 :     }
     780         252 :   }
     781             : 
     782             :   /* Update the callee accounts with any changes made by the caller prior to this CPI execution */
     783         252 :   ulong callee_account_keys[256];
     784         252 :   ulong caller_accounts_to_update[256];
     785         252 :   ulong caller_accounts_to_update_len = 0;
     786         252 :   err = VM_SYSCALL_CPI_TRANSLATE_AND_UPDATE_ACCOUNTS_FUNC( vm, instruction_accounts, instruction_accounts_cnt, acc_infos, acct_info_cnt, callee_account_keys, caller_accounts_to_update, &caller_accounts_to_update_len );
     787         252 :   if( FD_UNLIKELY( err ) ) return err;
     788             :   
     789             :   /* Set the transaction compute meter to be the same as the VM's compute meter,
     790             :      so that the callee cannot use compute units that the caller has already used. */
     791         252 :   vm->instr_ctx->txn_ctx->compute_meter = vm->cu;
     792             : 
     793             :   /* Execute the CPI instruction in the runtime */
     794         252 :   int err_exec = fd_execute_instr( vm->instr_ctx->txn_ctx, instruction_to_execute );
     795         252 :   ulong instr_exec_res = (ulong)err_exec;
     796             : 
     797             :   /* Set the CU meter to the instruction context's transaction context's compute meter,
     798             :      so that the caller can't use compute units that the callee has already used. */
     799         252 :   vm->cu = vm->instr_ctx->txn_ctx->compute_meter;
     800             : 
     801         252 :   *_ret = instr_exec_res;
     802             : 
     803         252 :   if( FD_UNLIKELY( err_exec ) ) return err_exec;
     804             : 
     805             :   /* https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/programs/bpf_loader/src/syscalls/cpi.rs#L1128-L1145 */
     806             :   /* Update all account permissions before updating the account data updates.
     807             :      We have inlined the anza function update_caller_account_perms here.
     808             :      TODO: consider factoring this out */
     809         240 :   if( vm->direct_mapping ) {
     810           0 :     for( ulong i=0UL; i<vm->instr_ctx->instr->acct_cnt; i++ ) {
     811             :       /* https://github.com/firedancer-io/solana/blob/508f325e19c0fd8e16683ea047d7c1a85f127e74/programs/bpf_loader/src/syscalls/cpi.rs#L939-L943 */
     812             :       /* Anza only even attemps to update the account permissions if it is a
     813             :          "caller account". Only writable accounts are caller accounts. */
     814           0 :       if( fd_instr_acc_is_writable_idx( vm->instr_ctx->instr, i ) ) {
     815             : 
     816           0 :         uint is_writable = (uint)fd_account_can_data_be_changed( vm->instr_ctx, i, &err );
     817             :         /* Lookup memory regions for the account data and the realloc region. */
     818           0 :         ulong data_region_idx    = vm->acc_region_metas[i].has_data_region ? vm->acc_region_metas[i].region_idx : 0;
     819           0 :         ulong realloc_region_idx = vm->acc_region_metas[i].has_resizing_region ? vm->acc_region_metas[i].region_idx : 0;
     820           0 :         if( data_region_idx && realloc_region_idx ) {
     821           0 :           realloc_region_idx++;
     822           0 :         }
     823             : 
     824           0 :         if( data_region_idx ) {
     825           0 :           vm->input_mem_regions[ data_region_idx ].is_writable = is_writable;
     826           0 :         }
     827           0 :         if( FD_LIKELY( realloc_region_idx ) ) { /* Unless is deprecated loader */
     828           0 :           vm->input_mem_regions[ realloc_region_idx ].is_writable = is_writable;
     829           0 :         }
     830           0 :       }
     831           0 :     }
     832           0 :   }
     833             : 
     834             :   /* Update the caller accounts with any changes made by the callee during CPI execution */
     835        1008 :   for( ulong i=0UL; i<caller_accounts_to_update_len; i++ ) {
     836             :     /* https://github.com/firedancer-io/solana/blob/508f325e19c0fd8e16683ea047d7c1a85f127e74/programs/bpf_loader/src/syscalls/cpi.rs#L939-L943 */
     837             :     /* We only want to update the writable accounts, because the non-writable 
     838             :        caller accounts can't be changed during a CPI execution. */
     839         768 :     if( fd_instr_acc_is_writable_idx( vm->instr_ctx->instr, callee_account_keys[i] ) ) {
     840         768 :       fd_pubkey_t const * callee = &vm->instr_ctx->instr->acct_pubkeys[callee_account_keys[i]];
     841         768 :       err = VM_SYSCALL_CPI_UPDATE_CALLER_ACC_FUNC(vm, &acc_infos[caller_accounts_to_update[i]], (uchar)callee_account_keys[i], callee);
     842         768 :       if( FD_UNLIKELY( err ) ) return err;
     843         768 :     }
     844         768 :   }
     845             : 
     846         240 :   return FD_VM_SUCCESS;
     847         240 : }
     848             : 
     849             : #undef VM_SYSCALL_CPI_UPDATE_CALLER_ACC_FUNC
     850             : #undef VM_SYSCALL_CPI_FROM_ACC_INFO_FUNC
     851             : #undef VM_SYSCALL_CPI_TRANSLATE_AND_UPDATE_ACCOUNTS_FUNC
     852             : #undef VM_SYSCALL_CPI_INSTRUCTION_TO_INSTR_FUNC
     853             : #undef VM_SYSCALL_CPI_FUNC

Generated by: LCOV version 1.14