LCOV - code coverage report
Current view: top level - flamenco/vm/syscall - fd_vm_syscall_cpi.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 109 228 47.8 %
Date: 2025-12-21 05:09:38 Functions: 8 11 72.7 %

          Line data    Source code
       1             : #include "fd_vm_syscall.h"
       2             : #include "../../runtime/fd_borrowed_account.h"
       3             : #include "../../runtime/fd_system_ids.h"
       4             : 
       5             : /* FIXME: ALGO EFFICIENCY */
       6             : static inline int
       7             : fd_vm_syscall_cpi_is_signer( fd_pubkey_t const * account,
       8             :            fd_pubkey_t const * signers,
       9           0 :            ulong               signers_cnt ) {
      10           0 :   for( ulong i=0UL; i<signers_cnt; i++ ) if( !memcmp( account->uc, signers[i].uc, sizeof(fd_pubkey_t) ) ) return 1;
      11           0 :   return 0;
      12           0 : }
      13             : 
      14             : /*
      15             : fd_vm_prepare_instruction populates instruction_accounts and instruction_accounts_cnt
      16             : with the instruction accounts ready for execution.
      17             : 
      18             : The majority of this logic is taken from
      19             : https://github.com/solana-labs/solana/blob/v1.17.22/program-runtime/src/invoke_context.rs#L535,
      20             : and is not vm-specific, but a part of the runtime.
      21             : TODO: should we move this out of the CPI section?
      22             : 
      23             : The bulk of the logic is concerned with unifying the privileges for each duplicated account,
      24             : ensuring that each duplicate account referenced has the same privileges. It also performs some
      25             : priviledge checks, for example ensuring the necessary signatures are present.
      26             : 
      27             : TODO: instruction calling convention: const parameters after non-const.
      28             : 
      29             : Assumptions:
      30             : - We do not have more than 256 unique accounts in the callee_instr.
      31             :   This limit comes from the fact that a Solana transaction cannot
      32             :   refefence more than 256 unique accounts, due to the transaction
      33             :   serialization format.
      34             : - callee_instr is not null.
      35             : - callee_instr->acct_pubkeys is at least as long as callee_instr->acct_cnt
      36             : - instr_ctx->txn_ctx->accounts_cnt is less than UCHAR_MAX.
      37             :   This is likely because the transaction is limited to 256 accounts.
      38             : - callee_instr->program_id is set to UCHAR_MAX if account is not in instr_ctx->txn_ctx.
      39             : - instruction_accounts is a 256-length empty array.
      40             : 
      41             : Parameters:
      42             : - callee_instr
      43             : - instr_ctx
      44             : - instruction_accounts
      45             : - instruction_accounts_cnt
      46             : - signers
      47             : - signers_cnt
      48             : 
      49             : Returns:
      50             : - instruction_accounts
      51             : - instruction_accounts_cnt
      52             : Populated with the instruction accounts with normalized permissions.
      53             : 
      54             : TODO: is it possible to pass the transaction indexes of the accounts in?
      55             : This would allow us to make some of these algorithms more efficient.
      56             : */
      57             : int
      58             : fd_vm_prepare_instruction( fd_instr_info_t *        callee_instr,
      59             :                            fd_exec_instr_ctx_t *    instr_ctx,
      60             :                            fd_pubkey_t const *      callee_program_id_pubkey,
      61             :                            fd_pubkey_t const        instr_acct_keys[ FD_INSTR_ACCT_MAX ],
      62             :                            fd_instruction_account_t instruction_accounts[ FD_INSTR_ACCT_MAX ],
      63             :                            ulong *                  instruction_accounts_cnt,
      64             :                            fd_pubkey_t const *      signers,
      65          36 :                            ulong                    signers_cnt ) {
      66             : 
      67             :   /* De-duplicate the instruction accounts, using the same logic as Solana */
      68          36 :   ulong deduplicated_instruction_accounts_cnt = 0;
      69          36 :   fd_instruction_account_t deduplicated_instruction_accounts[256] = {0};
      70          36 :   ulong duplicate_indicies_cnt = 0;
      71          36 :   ulong duplicate_indices[256] = {0};
      72             : 
      73             :   /* Normalize the privileges of each instruction account in the callee, after de-duping
      74             :      the account references.
      75             :     https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/program-runtime/src/invoke_context.rs#L540-L595 */
      76         108 :   for( ulong i=0UL; i<callee_instr->acct_cnt; i++ ) {
      77          72 :     ushort index_in_transaction = callee_instr->accounts[i].index_in_transaction;
      78          72 :     ushort index_in_caller      = callee_instr->accounts[i].index_in_caller;
      79             : 
      80          72 :     if( index_in_transaction==USHORT_MAX ) {
      81             :       /* In this case the callee instruction is referencing an unknown account not listed in the
      82             :          transactions accounts. */
      83           0 :       FD_BASE58_ENCODE_32_BYTES( instr_acct_keys[i].uc, id_b58 );
      84           0 :       fd_log_collector_msg_many( instr_ctx, 2, "Instruction references an unknown account ", 42UL, id_b58, id_b58_len );
      85           0 :       FD_TXN_ERR_FOR_LOG_INSTR( instr_ctx->txn_out, FD_EXECUTOR_INSTR_ERR_MISSING_ACC, instr_ctx->txn_out->err.exec_err_idx );
      86           0 :       return FD_EXECUTOR_INSTR_ERR_MISSING_ACC;
      87           0 :     }
      88             : 
      89             :     /* If there was an instruction account before this one which referenced the same
      90             :        transaction account index, find it's index in the deduplicated_instruction_accounts
      91             :        array. */
      92          72 :     ulong duplicate_index = ULONG_MAX;
      93         108 :     for( ulong j=0UL; j<deduplicated_instruction_accounts_cnt; j++ ) {
      94          36 :       if( deduplicated_instruction_accounts[j].index_in_transaction==index_in_transaction ) {
      95           0 :         duplicate_index = j;
      96           0 :         break;
      97           0 :       }
      98          36 :     }
      99             : 
     100             :     /* If this was account referenced in a previous iteration, update the flags to include those set
     101             :        in this iteration. This ensures that after all the iterations, the de-duplicated account flags
     102             :        for each account are the union of all the flags in all the references to that account in this instruction. */
     103             : 
     104             :     /* TODO: FD_UNLIKELY? Need to check which branch is more common by running against a larger mainnet ledger */
     105             :     /* TODO: this code would maybe be easier to read if we inverted the branches */
     106          72 :     if( duplicate_index!=ULONG_MAX ) {
     107           0 :       if ( FD_UNLIKELY( duplicate_index >= deduplicated_instruction_accounts_cnt ) ) {
     108           0 :         FD_TXN_ERR_FOR_LOG_INSTR( instr_ctx->txn_out, FD_EXECUTOR_INSTR_ERR_MISSING_ACC, instr_ctx->txn_out->err.exec_err_idx );
     109           0 :         return FD_EXECUTOR_INSTR_ERR_MISSING_ACC;
     110           0 :       }
     111             : 
     112           0 :       duplicate_indices[duplicate_indicies_cnt++] = duplicate_index;
     113           0 :       fd_instruction_account_t * instruction_account = &deduplicated_instruction_accounts[duplicate_index];
     114           0 :       instruction_account->is_signer   = !!(instruction_account->is_signer   | callee_instr->accounts[i].is_signer);
     115           0 :       instruction_account->is_writable = !!(instruction_account->is_writable | callee_instr->accounts[i].is_writable);
     116          72 :     } else {
     117             :       /* In the case where the callee instruction is NOT a duplicate, we need to
     118             :          create the deduplicated_instruction_accounts fd_instruction_account_t object. */
     119             : 
     120             :       /* Add the instruction account to the duplicate indicies array */
     121          72 :       duplicate_indices[duplicate_indicies_cnt++] = deduplicated_instruction_accounts_cnt;
     122             : 
     123             :       /* Initialize the instruction account in the deduplicated_instruction_accounts array */
     124          72 :       fd_instruction_account_t * instruction_account = &deduplicated_instruction_accounts[deduplicated_instruction_accounts_cnt++];
     125          72 :       *instruction_account = fd_instruction_account_init( index_in_transaction,
     126          72 :                                                           index_in_caller,
     127          72 :                                                           (ushort)i,
     128          72 :                                                           !!(callee_instr->accounts[i].is_writable),
     129          72 :                                                           !!(callee_instr->accounts[i].is_signer) );
     130          72 :     }
     131          72 :   }
     132             : 
     133             :   /* Check the normalized account permissions for privilege escalation.
     134             :      https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/program-runtime/src/invoke_context.rs#L596-L624 */
     135         108 :   for( ulong i = 0; i < deduplicated_instruction_accounts_cnt; i++ ) {
     136          72 :     fd_instruction_account_t * instruction_account = &deduplicated_instruction_accounts[i];
     137             : 
     138             :     /* https://github.com/anza-xyz/agave/blob/v2.1.14/program-runtime/src/invoke_context.rs#L390-L393 */
     139          72 :     fd_guarded_borrowed_account_t borrowed_caller_acct = {0};
     140          72 :     FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( instr_ctx, instruction_account->index_in_caller, &borrowed_caller_acct );
     141             : 
     142             :     /* Check that the account is not read-only in the caller but writable in the callee */
     143          72 :     if( FD_UNLIKELY( instruction_account->is_writable && !fd_borrowed_account_is_writable( &borrowed_caller_acct ) ) ) {
     144           0 :       FD_BASE58_ENCODE_32_BYTES( borrowed_caller_acct.pubkey->uc, id_b58 );
     145           0 :       fd_log_collector_msg_many( instr_ctx, 2, id_b58, id_b58_len, "'s writable privilege escalated", 31UL );
     146           0 :       FD_TXN_ERR_FOR_LOG_INSTR( instr_ctx->txn_out, FD_EXECUTOR_INSTR_ERR_PRIVILEGE_ESCALATION, instr_ctx->txn_out->err.exec_err_idx );
     147           0 :       return FD_EXECUTOR_INSTR_ERR_PRIVILEGE_ESCALATION;
     148           0 :     }
     149             : 
     150             :     /* If the account is signed in the callee, it must be signed by the caller or the program */
     151          72 :     if ( FD_UNLIKELY( instruction_account->is_signer && !( fd_borrowed_account_is_signer( &borrowed_caller_acct ) || fd_vm_syscall_cpi_is_signer( borrowed_caller_acct.pubkey, signers, signers_cnt) ) ) ) {
     152           0 :       FD_BASE58_ENCODE_32_BYTES( borrowed_caller_acct.pubkey->uc, id_b58 );
     153           0 :       fd_log_collector_msg_many( instr_ctx, 2, id_b58, id_b58_len, "'s signer privilege escalated", 29UL );
     154           0 :       FD_TXN_ERR_FOR_LOG_INSTR( instr_ctx->txn_out, FD_EXECUTOR_INSTR_ERR_PRIVILEGE_ESCALATION, instr_ctx->txn_out->err.exec_err_idx );
     155           0 :       return FD_EXECUTOR_INSTR_ERR_PRIVILEGE_ESCALATION;
     156           0 :     }
     157          72 :   }
     158             : 
     159             :   /* Copy the accounts with their normalized permissions over to the final instruction_accounts array,
     160             :      and set the callee_instr acct_flags. */
     161         108 :   for (ulong i = 0; i < duplicate_indicies_cnt; i++) {
     162          72 :     ulong duplicate_index = duplicate_indices[i];
     163             : 
     164             :     /* Failing this condition is technically impossible, but it is probably safest to keep this in
     165             :        so that we throw InstructionError::NotEnoughAccountKeys at the same point at Solana does,
     166             :        in the event any surrounding code is changed.
     167             :        https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/program-runtime/src/invoke_context.rs#L625-L633 */
     168          72 :     if ( FD_LIKELY( duplicate_index < deduplicated_instruction_accounts_cnt ) ) {
     169          72 :       instruction_accounts[i] = deduplicated_instruction_accounts[duplicate_index];
     170          72 :       callee_instr->accounts[i].is_writable = !!(instruction_accounts[i].is_writable);
     171          72 :       callee_instr->accounts[i].is_signer   = !!(instruction_accounts[i].is_signer);
     172          72 :     } else {
     173           0 :       FD_TXN_ERR_FOR_LOG_INSTR( instr_ctx->txn_out, FD_EXECUTOR_INSTR_ERR_MISSING_ACC, instr_ctx->txn_out->err.exec_err_idx );
     174           0 :       return FD_EXECUTOR_INSTR_ERR_MISSING_ACC;
     175           0 :     }
     176          72 :   }
     177             : 
     178             :   /* Obtain the program account index and return a MissingAccount error if not found.
     179             :     https://github.com/anza-xyz/agave/blob/v2.1.14/program-runtime/src/invoke_context.rs#L430-L435 */
     180          36 :   int program_idx = fd_exec_instr_ctx_find_idx_of_instr_account( instr_ctx, callee_program_id_pubkey );
     181          36 :   if( FD_UNLIKELY( program_idx == -1 ) ) {
     182           0 :     FD_BASE58_ENCODE_32_BYTES( callee_program_id_pubkey->uc, id_b58 );
     183           0 :     fd_log_collector_msg_many( instr_ctx, 2, "Unknown program ", 16UL, id_b58, id_b58_len );
     184           0 :     FD_TXN_ERR_FOR_LOG_INSTR( instr_ctx->txn_out, FD_EXECUTOR_INSTR_ERR_MISSING_ACC, instr_ctx->txn_out->err.exec_err_idx );
     185           0 :     return FD_EXECUTOR_INSTR_ERR_MISSING_ACC;
     186           0 :   }
     187             : 
     188             :   /* Caller is in charge of setting an appropriate sentinel value (i.e., UCHAR_MAX) for callee_instr->program_id if not found.
     189             :     Borrow the program account here.
     190             :     https://github.com/anza-xyz/agave/blob/v2.1.14/program-runtime/src/invoke_context.rs#L436-L437 */
     191          36 :   fd_guarded_borrowed_account_t borrowed_program_account = {0};
     192          36 :   int err = fd_exec_instr_ctx_try_borrow_instr_account( instr_ctx, (ushort)program_idx, &borrowed_program_account );
     193          36 :   if( FD_UNLIKELY( err ) ) {
     194           0 :     FD_TXN_ERR_FOR_LOG_INSTR( instr_ctx->txn_out, err, instr_ctx->txn_out->err.exec_err_idx );
     195           0 :     return err;
     196           0 :   }
     197             : 
     198          36 :   if( FD_UNLIKELY( err ) ) {
     199             :     /* https://github.com/anza-xyz/agave/blob/a9ac3f55fcb2bc735db0d251eda89897a5dbaaaa/program-runtime/src/invoke_context.rs#L434 */
     200           0 :     FD_BASE58_ENCODE_32_BYTES( callee_program_id_pubkey->uc, id_b58 );
     201           0 :     fd_log_collector_msg_many( instr_ctx, 2, "Unknown program ", 16UL, id_b58, id_b58_len );
     202           0 :     FD_TXN_ERR_FOR_LOG_INSTR( instr_ctx->txn_out, FD_EXECUTOR_INSTR_ERR_MISSING_ACC, instr_ctx->txn_out->err.exec_err_idx );
     203           0 :     return FD_EXECUTOR_INSTR_ERR_MISSING_ACC;
     204           0 :   }
     205             : 
     206          36 :   *instruction_accounts_cnt = duplicate_indicies_cnt;
     207             : 
     208          36 :   return 0;
     209          36 : }
     210             : 
     211             : /**********************************************************************
     212             :    CROSS PROGRAM INVOCATION (Generic logic)
     213             :  **********************************************************************/
     214             : 
     215             : /* FD_CPI_MAX_SIGNER_CNT is the max amount of PDA signer addresses that
     216             :    a cross-program invocation can include in an instruction.
     217             : 
     218             :    https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/programs/bpf_loader/src/syscalls/mod.rs#L80 */
     219             : 
     220             : #define FD_CPI_MAX_SIGNER_CNT              (16UL)
     221             : 
     222             : /* "Maximum number of account info structs that can be used in a single CPI
     223             :    invocation. A limit on account info structs is effectively the same as
     224             :    limiting the number of unique accounts. 128 was chosen to match the max
     225             :    number of locked accounts per transaction (MAX_TX_ACCOUNT_LOCKS)."
     226             : 
     227             :    https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/sdk/program/src/syscalls/mod.rs#L25
     228             :    https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/programs/bpf_loader/src/syscalls/cpi.rs#L1011 */
     229             : 
     230           9 : #define FD_CPI_MAX_ACCOUNT_INFOS           (128UL)
     231          18 : #define FD_CPI_MAX_ACCOUNT_INFOS_SIMD_0339 (255UL)
     232             : 
     233             : /* This is just encoding what Agave says in their code comments into a
     234             :    compile-time check, so if anyone ever inadvertently changes one of
     235             :    the limits, they will have to take a look. */
     236             : FD_STATIC_ASSERT( FD_CPI_MAX_ACCOUNT_INFOS==MAX_TX_ACCOUNT_LOCKS, cpi_max_account_info );
     237             : 
     238             : /* https://github.com/anza-xyz/agave/blob/v3.1.2/program-runtime/src/cpi.rs#L168-L180 */
     239             : static inline ulong
     240          36 : get_cpi_max_account_infos( fd_bank_t * bank ) {
     241          36 :   if( FD_LIKELY( FD_FEATURE_ACTIVE_BANK( bank, increase_cpi_account_info_limit ) ) ) {
     242          18 :     return FD_CPI_MAX_ACCOUNT_INFOS_SIMD_0339;
     243          18 :   } else if( FD_LIKELY( FD_FEATURE_ACTIVE_BANK( bank, increase_tx_account_lock_limit ) ) ) {
     244           9 :     return FD_CPI_MAX_ACCOUNT_INFOS;
     245           9 :   } else {
     246           9 :     return 64UL;
     247           9 :   }
     248          36 : }
     249             : 
     250             : /* https://github.com/anza-xyz/agave/blob/v3.1.2/program-runtime/src/execution_budget.rs#L25-L31 */
     251             : static inline ulong
     252          36 : get_cpi_invoke_unit_cost( fd_bank_t * bank ) {
     253          36 :   return fd_ulong_if(
     254          36 :     FD_FEATURE_ACTIVE_BANK( bank, increase_cpi_account_info_limit ),
     255          36 :     FD_VM_INVOKE_UNITS_SIMD_0339,
     256          36 :     FD_VM_INVOKE_UNITS );
     257          36 : }
     258             : 
     259             : /* Maximum CPI instruction accounts. 255 was chosen to ensure that instruction
     260             :    accounts are always within the maximum instruction account limit for BPF
     261             :    program instructions.
     262             : 
     263             :    https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/sdk/program/src/syscalls/mod.rs#L19
     264             :    https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/programs/bpf_loader/src/serialization.rs#L26 */
     265             : 
     266             : #define FD_CPI_MAX_INSTRUCTION_ACCOUNTS    (255UL)
     267             : 
     268             : /* fd_vm_syscall_cpi_check_instruction contains common instruction acct
     269             :    count and data sz checks.  Also consumes compute units proportional
     270             :    to instruction data size. */
     271             : 
     272             : static int
     273             : fd_vm_syscall_cpi_check_instruction( fd_vm_t const * vm,
     274             :                                      ulong           acct_cnt,
     275          36 :                                      ulong           data_sz ) {
     276             :   /* https://github.com/anza-xyz/agave/blob/v3.1.2/program-runtime/src/cpi.rs#L146-L161 */
     277          36 :   if( FD_FEATURE_ACTIVE_BANK( vm->instr_ctx->bank, loosen_cpi_size_restriction ) ) {
     278          36 :     if( FD_UNLIKELY( acct_cnt > FD_CPI_MAX_INSTRUCTION_ACCOUNTS ) ) {
     279             :       // SyscallError::MaxInstructionAccountsExceeded
     280           0 :       return FD_VM_SYSCALL_ERR_MAX_INSTRUCTION_ACCOUNTS_EXCEEDED;
     281           0 :     }
     282          36 :     if( FD_UNLIKELY( data_sz>FD_RUNTIME_CPI_MAX_INSTR_DATA_LEN ) ) {
     283             :       // SyscallError::MaxInstructionDataLenExceeded
     284           0 :       return FD_VM_SYSCALL_ERR_MAX_INSTRUCTION_DATA_LEN_EXCEEDED;
     285           0 :     }
     286          36 :   } else {
     287             :     // https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/programs/bpf_loader/src/syscalls/cpi.rs#L1114
     288           0 :     ulong tot_sz = fd_ulong_sat_add( fd_ulong_sat_mul( FD_VM_RUST_ACCOUNT_META_SIZE, acct_cnt ), data_sz );
     289           0 :     if ( FD_UNLIKELY( tot_sz > FD_VM_MAX_CPI_INSTRUCTION_SIZE ) ) {
     290             :       // SyscallError::InstructionTooLarge
     291           0 :       return FD_VM_SYSCALL_ERR_INSTRUCTION_TOO_LARGE;
     292           0 :     }
     293           0 :   }
     294             : 
     295          36 :   return FD_VM_SUCCESS;
     296          36 : }
     297             : 
     298             : /* https://github.com/anza-xyz/agave/blob/v3.0.1/syscalls/src/cpi.rs#L1134-L1169 */
     299             : static inline int
     300             : fd_vm_cpi_update_caller_account_region( fd_vm_t *                    vm,
     301             :                                         ulong                        instr_acc_idx,
     302             :                                         fd_vm_cpi_caller_account_t * caller_account,
     303           0 :                                         fd_borrowed_account_t *      borrowed_account ) {
     304             :   /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1141-L1148 */
     305           0 :   ulong address_space_reserved_for_account;
     306           0 :   if( vm->stricter_abi_and_runtime_constraints && vm->is_deprecated ) {
     307           0 :     address_space_reserved_for_account = caller_account->orig_data_len;
     308           0 :   } else {
     309           0 :     address_space_reserved_for_account = fd_ulong_sat_add( caller_account->orig_data_len, MAX_PERMITTED_DATA_INCREASE );
     310           0 :   }
     311             : 
     312             :   /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1159-L1164 */
     313           0 :   if( address_space_reserved_for_account > 0UL ) {
     314             :     /* Note that we don't special-case direct mapping here, as Agave does,
     315             :        because we do not create regions using CoW upon resize like Agave does.
     316             : 
     317             :        Therefore we do not need the logic in the Agave code to create a new
     318             :        region, as we have already created all the regions for each account.
     319             : 
     320             :        Therefore we do not have equivalents of Agave's
     321             :        modify_memory_region_of_account and create_memory_region_of_account
     322             :        functions, but we instead inline this logic directly below. */
     323           0 :     fd_vm_acc_region_meta_t * acc_region_meta = &vm->acc_region_metas[instr_acc_idx];
     324           0 :     fd_vm_input_region_t *    region          = &vm->input_mem_regions[acc_region_meta->region_idx + 1UL];
     325             : 
     326             :     /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1159-L1165 */
     327             :     /* https://github.com/anza-xyz/agave/blob/v3.0.4/program-runtime/src/serialization.rs#L23-L35 */
     328           0 :     region->region_sz = (uint)fd_borrowed_account_get_data_len( borrowed_account );
     329             : 
     330           0 :     int err;
     331           0 :     int is_writable = fd_borrowed_account_can_data_be_changed( borrowed_account, &err );
     332             : 
     333           0 :     region->is_writable = (uchar)is_writable && ( err == FD_EXECUTOR_INSTR_SUCCESS );
     334           0 :   }
     335             : 
     336           0 :   return FD_VM_SUCCESS;
     337           0 : }
     338             : 
     339             : /**********************************************************************
     340             :   CROSS PROGRAM INVOCATION HELPERS
     341             :  **********************************************************************/
     342             : 
     343             : static inline int
     344             : fd_vm_syscall_cpi_check_id( fd_pubkey_t const * program_id,
     345         252 :           uchar const * loader ) {
     346         252 :   return !memcmp( program_id, loader, sizeof(fd_pubkey_t) );
     347         252 : }
     348             : 
     349             : /* fd_vm_syscall_cpi_is_precompile returns true if the given program_id
     350             :    corresponds to a precompile. It does this by checking against a hardcoded
     351             :    list of known pre-compiles.
     352             : 
     353             :    This mirrors the behaviour in solana_sdk::precompiles::is_precompile
     354             :    https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/sdk/src/precompiles.rs#L93
     355             :  */
     356             : static inline int
     357          36 : fd_vm_syscall_cpi_is_precompile( fd_pubkey_t const * program_id, fd_bank_t * bank ) {
     358          36 :   return fd_vm_syscall_cpi_check_id(program_id, fd_solana_keccak_secp_256k_program_id.key) |
     359          36 :          fd_vm_syscall_cpi_check_id(program_id, fd_solana_ed25519_sig_verify_program_id.key) |
     360          36 :          ( fd_vm_syscall_cpi_check_id(program_id, fd_solana_secp256r1_program_id.key) &&
     361          36 :            FD_FEATURE_ACTIVE_BANK( bank, enable_secp256r1_precompile ) );
     362          36 : }
     363             : 
     364             : /* fd_vm_syscall_cpi_check_authorized_program corresponds to
     365             : solana_bpf_loader_program::syscalls::cpi::check_authorized_program:
     366             : https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/programs/bpf_loader/src/syscalls/cpi.rs#L1032
     367             : 
     368             : It determines if the given program_id is authorized to execute a CPI call.
     369             : 
     370             : FIXME: return type
     371             :  */
     372             : static inline ulong
     373             : fd_vm_syscall_cpi_check_authorized_program( fd_pubkey_t const * program_id,
     374             :                                             fd_bank_t *         bank,
     375             :                                             uchar const *       instruction_data,
     376          36 :                                             ulong               instruction_data_len ) {
     377             :   /* FIXME: do this in a branchless manner? using bitwise comparison would probably be faster */
     378          36 :   return ( fd_vm_syscall_cpi_check_id(program_id, fd_solana_native_loader_id.key) ||
     379          36 :            fd_vm_syscall_cpi_check_id(program_id, fd_solana_bpf_loader_program_id.key) ||
     380          36 :            fd_vm_syscall_cpi_check_id(program_id, fd_solana_bpf_loader_deprecated_program_id.key) ||
     381          36 :            ( fd_vm_syscall_cpi_check_id(program_id, fd_solana_bpf_loader_upgradeable_program_id.key) &&
     382          36 :              !(( instruction_data_len != 0 && instruction_data[0] == fd_bpf_upgradeable_loader_program_instruction_enum_upgrade ) ||
     383           0 :                ( instruction_data_len != 0 && instruction_data[0] == fd_bpf_upgradeable_loader_program_instruction_enum_set_authority ) ||
     384           0 :                ( FD_FEATURE_ACTIVE_BANK( bank, enable_bpf_loader_set_authority_checked_ix ) &&
     385           0 :                  ( instruction_data_len != 0 && instruction_data[0] == fd_bpf_upgradeable_loader_program_instruction_enum_set_authority_checked )) ||
     386           0 :                ( FD_FEATURE_ACTIVE_BANK( bank, enable_extend_program_checked ) &&
     387           0 :                  ( instruction_data_len != 0 && instruction_data[0] == fd_bpf_upgradeable_loader_program_instruction_enum_extend_program_checked )) ||
     388           0 :                ( instruction_data_len != 0 && instruction_data[0] == fd_bpf_upgradeable_loader_program_instruction_enum_close ))) ||
     389          36 :            fd_vm_syscall_cpi_is_precompile( program_id, bank ) );
     390          36 : }
     391             : 
     392             : /* The data and lamports fields are in an Rc<Refcell<T>> in the Rust ABI AccountInfo.
     393             :    These macros perform the equivalent of Rc<Refcell<T>>.as_ptr() in Agave.
     394             :    This function doesn't actually touch any memory.
     395             :    It performs pointer arithmetic.
     396             :  */
     397             : FD_FN_CONST static inline
     398           0 : ulong vm_syscall_cpi_acc_info_rc_refcell_as_ptr( ulong rc_refcell_vaddr ) {
     399           0 :   return (ulong) &(((fd_vm_rc_refcell_t *)rc_refcell_vaddr)->payload);
     400           0 : }
     401             : 
     402             : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L310-L316
     403             :  */
     404             : FD_FN_CONST static inline
     405          48 : ulong vm_syscall_cpi_data_len_vaddr_c( ulong acct_info_vaddr, ulong data_len_haddr, ulong acct_info_haddr ) {
     406          48 :   return fd_ulong_sat_sub( fd_ulong_sat_add( acct_info_vaddr, data_len_haddr ), acct_info_haddr );
     407          48 : }
     408             : 
     409             : /**********************************************************************
     410             :   CROSS PROGRAM INVOCATION (C ABI)
     411             :  **********************************************************************/
     412             : 
     413             : #define VM_SYSCALL_CPI_ABI                     c
     414          36 : #define VM_SYSCALL_CPI_INSTR_T                 fd_vm_c_instruction_t
     415             : #define VM_SYSCALL_CPI_INSTR_ALIGN             (FD_VM_C_INSTRUCTION_ALIGN)
     416             : #define VM_SYSCALL_CPI_INSTR_SIZE              (FD_VM_C_INSTRUCTION_SIZE)
     417         180 : #define VM_SYSCALL_CPI_ACC_META_T              fd_vm_c_account_meta_t
     418             : #define VM_SYSCALL_CPI_ACC_META_ALIGN          (FD_VM_C_ACCOUNT_META_ALIGN)
     419             : #define VM_SYSCALL_CPI_ACC_META_SIZE           (FD_VM_C_ACCOUNT_META_SIZE)
     420          36 : #define VM_SYSCALL_CPI_ACC_INFO_T              fd_vm_c_account_info_t
     421             : #define VM_SYSCALL_CPI_ACC_INFO_ALIGN          (FD_VM_C_ACCOUNT_INFO_ALIGN)
     422          84 : #define VM_SYSCALL_CPI_ACC_INFO_SIZE           (FD_VM_C_ACCOUNT_INFO_SIZE)
     423             : 
     424             : /* VM_SYSCALL_CPI_INSTR_T accessors */
     425             : #define VM_SYSCALL_CPI_INSTR_DATA_ADDR( instr ) instr->data_addr
     426         108 : #define VM_SYSCALL_CPI_INSTR_DATA_LEN( instr )  instr->data_len
     427             : #define VM_SYSCALL_CPI_INSTR_ACCS_ADDR( instr ) instr->accounts_addr
     428         306 : #define VM_SYSCALL_CPI_INSTR_ACCS_LEN( instr )  instr->accounts_len
     429             : #define VM_SYSCALL_CPI_INSTR_PROGRAM_ID( vm, instr ) \
     430          36 :   FD_VM_MEM_HADDR_LD( vm, instr->program_id_addr, alignof(uchar), sizeof(fd_pubkey_t)  )
     431             : 
     432             : /* VM_SYSCALL_CPI_ACC_META_T accessors */
     433         144 : #define VM_SYSCALL_CPI_ACC_META_IS_WRITABLE( acc_meta ) acc_meta->is_writable
     434         144 : #define VM_SYSCALL_CPI_ACC_META_IS_SIGNER( acc_meta ) acc_meta->is_signer
     435             : #define VM_SYSCALL_CPI_ACC_META_PUBKEY( vm, acc_meta ) \
     436         144 :   FD_VM_MEM_HADDR_LD( vm, acc_meta->pubkey_addr, alignof(uchar), sizeof(fd_pubkey_t) )
     437             : 
     438             : /* VM_SYSCALL_CPI_ACC_INFO_T accessors */
     439             : #define VM_SYSCALL_CPI_ACC_INFO_LAMPORTS_VADDR( vm, acc_info, decl ) \
     440          48 :   ulong decl = acc_info->lamports_addr;
     441             : #define VM_SYSCALL_CPI_ACC_INFO_LAMPORTS( vm, acc_info, decl ) \
     442          48 :   ulong * decl = FD_VM_MEM_HADDR_ST( vm, acc_info->lamports_addr, alignof(ulong), sizeof(ulong) );
     443             : 
     444             : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L304 */
     445             : #define VM_SYSCALL_CPI_ACC_INFO_DATA_VADDR( vm, acc_info, decl ) \
     446          48 :   ulong decl = acc_info->data_addr;
     447             : #define VM_SYSCALL_CPI_ACC_INFO_DATA( vm, acc_info, decl ) \
     448          48 :   uchar * decl = FD_VM_MEM_SLICE_HADDR_ST( vm, acc_info->data_addr, alignof(uchar), acc_info->data_sz ); \
     449          48 :   ulong FD_EXPAND_THEN_CONCAT2(decl, _vm_addr) = acc_info->data_addr; \
     450          48 :   ulong FD_EXPAND_THEN_CONCAT2(decl, _len) = acc_info->data_sz;
     451             : 
     452             : #define VM_SYSCALL_CPI_ACC_INFO_METADATA( vm, acc_info, decl ) \
     453             :   ulong FD_EXPAND_THEN_CONCAT2(decl, _vm_addr) = acc_info->data_addr; \
     454             :   ulong FD_EXPAND_THEN_CONCAT2(decl, _len) = acc_info->data_sz;
     455             : 
     456             : #define VM_SYSCALL_CPI_SET_ACC_INFO_DATA_GET_LEN( vm, acc_info, decl ) \
     457          48 :   ulong FD_EXPAND_THEN_CONCAT2(decl, _len) = acc_info->data_sz;
     458             : 
     459             : #include "fd_vm_syscall_cpi_common.c"
     460             : 
     461             : #undef VM_SYSCALL_CPI_ABI
     462             : #undef VM_SYSCALL_CPI_INSTR_T
     463             : #undef VM_SYSCALL_CPI_INSTR_ALIGN
     464             : #undef VM_SYSCALL_CPI_INSTR_SIZE
     465             : #undef VM_SYSCALL_CPI_ACC_META_T
     466             : #undef VM_SYSCALL_CPI_ACC_META_ALIGN
     467             : #undef VM_SYSCALL_CPI_ACC_META_SIZE
     468             : #undef VM_SYSCALL_CPI_ACC_INFO_T
     469             : #undef VM_SYSCALL_CPI_ACC_INFO_ALIGN
     470             : #undef VM_SYSCALL_CPI_ACC_INFO_SIZE
     471             : #undef VM_SYSCALL_CPI_INSTR_DATA_ADDR
     472             : #undef VM_SYSCALL_CPI_INSTR_DATA_LEN
     473             : #undef VM_SYSCALL_CPI_INSTR_ACCS_ADDR
     474             : #undef VM_SYSCALL_CPI_INSTR_ACCS_LEN
     475             : #undef VM_SYSCALL_CPI_INSTR_PROGRAM_ID
     476             : #undef VM_SYSCALL_CPI_ACC_META_IS_WRITABLE
     477             : #undef VM_SYSCALL_CPI_ACC_META_IS_SIGNER
     478             : #undef VM_SYSCALL_CPI_ACC_META_PUBKEY
     479             : #undef VM_SYSCALL_CPI_ACC_INFO_LAMPORTS_VADDR
     480             : #undef VM_SYSCALL_CPI_ACC_INFO_LAMPORTS
     481             : #undef VM_SYSCALL_CPI_ACC_INFO_DATA_VADDR
     482             : #undef VM_SYSCALL_CPI_ACC_INFO_DATA
     483             : #undef VM_SYSCALL_CPI_ACC_INFO_METADATA
     484             : #undef VM_SYSCALL_CPI_SET_ACC_INFO_DATA_GET_LEN
     485             : 
     486             : /**********************************************************************
     487             :    CROSS PROGRAM INVOCATION (Rust ABI)
     488             :  **********************************************************************/
     489             : 
     490             : #define VM_SYSCALL_CPI_ABI                     rust
     491           0 : #define VM_SYSCALL_CPI_INSTR_T                 fd_vm_rust_instruction_t
     492             : #define VM_SYSCALL_CPI_INSTR_ALIGN             (FD_VM_RUST_INSTRUCTION_ALIGN)
     493             : #define VM_SYSCALL_CPI_INSTR_SIZE              (FD_VM_RUST_INSTRUCTION_SIZE)
     494           0 : #define VM_SYSCALL_CPI_ACC_META_T              fd_vm_rust_account_meta_t
     495             : #define VM_SYSCALL_CPI_ACC_META_ALIGN          (FD_VM_RUST_ACCOUNT_META_ALIGN)
     496             : #define VM_SYSCALL_CPI_ACC_META_SIZE           (FD_VM_RUST_ACCOUNT_META_SIZE)
     497           0 : #define VM_SYSCALL_CPI_ACC_INFO_T              fd_vm_rust_account_info_t
     498             : #define VM_SYSCALL_CPI_ACC_INFO_ALIGN          (FD_VM_RUST_ACCOUNT_INFO_ALIGN)
     499           0 : #define VM_SYSCALL_CPI_ACC_INFO_SIZE           (FD_VM_RUST_ACCOUNT_INFO_SIZE)
     500             : 
     501             : /* VM_SYSCALL_CPI_INSTR_T accessors */
     502             : #define VM_SYSCALL_CPI_INSTR_DATA_ADDR( instr ) instr->data.addr
     503           0 : #define VM_SYSCALL_CPI_INSTR_DATA_LEN( instr )  instr->data.len
     504             : #define VM_SYSCALL_CPI_INSTR_ACCS_ADDR( instr ) instr->accounts.addr
     505           0 : #define VM_SYSCALL_CPI_INSTR_ACCS_LEN( instr )  instr->accounts.len
     506           0 : #define VM_SYSCALL_CPI_INSTR_PROGRAM_ID( vm, instr ) instr->pubkey
     507             : 
     508             : /* VM_SYSCALL_CPI_ACC_META_T accessors */
     509           0 : #define VM_SYSCALL_CPI_ACC_META_IS_WRITABLE( acc_meta ) acc_meta->is_writable
     510           0 : #define VM_SYSCALL_CPI_ACC_META_IS_SIGNER( acc_meta ) acc_meta->is_signer
     511           0 : #define VM_SYSCALL_CPI_ACC_META_PUBKEY( vm, acc_meta ) acc_meta->pubkey
     512             : 
     513             : /* VM_SYSCALL_CPI_ACC_INFO_T accessors */
     514             : 
     515             : /* The lamports and the account data are stored behind RefCells,
     516             :    so we have an additional layer of indirection to unwrap. */
     517             : #define VM_SYSCALL_CPI_ACC_INFO_LAMPORTS_VADDR( vm, acc_info, decl )                                                                             \
     518           0 :     ulong const * FD_EXPAND_THEN_CONCAT2(decl, _hptr_) =                                                                                         \
     519           0 :       FD_VM_MEM_HADDR_LD( vm, vm_syscall_cpi_acc_info_rc_refcell_as_ptr( acc_info->lamports_box_addr ), FD_VM_RC_REFCELL_ALIGN, sizeof(ulong) ); \
     520           0 :     /* Extract the vaddr embedded in the RefCell */                                                                                              \
     521           0 :     ulong decl = *FD_EXPAND_THEN_CONCAT2(decl, _hptr_);
     522             : 
     523             : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L184-L195 */
     524             : #define VM_SYSCALL_CPI_ACC_INFO_LAMPORTS( vm, acc_info, decl )                                                                                                     \
     525           0 :     ulong FD_EXPAND_THEN_CONCAT2(decl, _vaddr_) =                                                                                                                  \
     526           0 :       *((ulong const *)FD_VM_MEM_HADDR_LD( vm, vm_syscall_cpi_acc_info_rc_refcell_as_ptr( acc_info->lamports_box_addr ), FD_VM_RC_REFCELL_ALIGN, sizeof(ulong) )); \
     527           0 :     ulong * decl = FD_VM_MEM_HADDR_ST( vm, FD_EXPAND_THEN_CONCAT2(decl, _vaddr_), alignof(ulong), sizeof(ulong) );
     528             : 
     529             : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L184-L195 */
     530             : #define VM_SYSCALL_CPI_ACC_INFO_DATA_VADDR( vm, acc_info, decl )                                                                                   \
     531           0 :     if( FD_UNLIKELY( vm->stricter_abi_and_runtime_constraints && acc_info->data_box_addr >= FD_VM_MEM_MAP_INPUT_REGION_START ) ) {                 \
     532           0 :       FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_INVALID_POINTER );                                                                          \
     533           0 :       return FD_VM_SYSCALL_ERR_INVALID_POINTER;                                                                                                    \
     534           0 :     }                                                                                                                                              \
     535           0 :     /* Translate the vaddr to the slice */                                                                                                         \
     536           0 :     fd_vm_vec_t const * FD_EXPAND_THEN_CONCAT2(decl, _hptr_) =                                                                                     \
     537           0 :       FD_VM_MEM_HADDR_LD( vm, vm_syscall_cpi_acc_info_rc_refcell_as_ptr( acc_info->data_box_addr ), FD_VM_RC_REFCELL_ALIGN, sizeof(fd_vm_vec_t) ); \
     538           0 :     /* Extract the vaddr embedded in the slice */                                                                                                  \
     539           0 :     ulong decl = FD_EXPAND_THEN_CONCAT2(decl, _hptr_)->addr;
     540             : 
     541             : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L212-L221 */
     542             : #define VM_SYSCALL_CPI_ACC_INFO_DATA_LEN_VADDR( vm, acc_info, decl ) \
     543           0 :     ulong decl = fd_ulong_sat_add( vm_syscall_cpi_acc_info_rc_refcell_as_ptr( acc_info->data_box_addr ), sizeof(ulong) );
     544             : 
     545             : #define VM_SYSCALL_CPI_ACC_INFO_DATA( vm, acc_info, decl )                                                                                         \
     546             :     /* Translate the vaddr to the slice */                                                                                                         \
     547           0 :     fd_vm_vec_t const * FD_EXPAND_THEN_CONCAT2(decl, _hptr_) =                                                                                     \
     548           0 :       FD_VM_MEM_HADDR_LD( vm, vm_syscall_cpi_acc_info_rc_refcell_as_ptr( acc_info->data_box_addr ), FD_VM_RC_REFCELL_ALIGN, sizeof(fd_vm_vec_t) ); \
     549           0 :     /* Declare the vaddr of the slice's underlying byte array */                                                                                   \
     550           0 :     ulong FD_EXPAND_THEN_CONCAT2(decl, _vm_addr) = FD_EXPAND_THEN_CONCAT2(decl, _hptr_)->addr;                                                     \
     551           0 :     /* Declare the size of the slice's underlying byte array */                                                                                    \
     552           0 :     ulong FD_EXPAND_THEN_CONCAT2(decl, _len) = FD_EXPAND_THEN_CONCAT2(decl, _hptr_)->len;                                                          \
     553           0 :     /* Translate the vaddr to the underlying byte array */                                                                                         \
     554           0 :     uchar * decl = FD_VM_MEM_SLICE_HADDR_ST(                                                                                                       \
     555           0 :       vm, FD_EXPAND_THEN_CONCAT2(decl, _hptr_)->addr, alignof(uchar), FD_EXPAND_THEN_CONCAT2(decl, _hptr_)->len );
     556             : 
     557             : #define VM_SYSCALL_CPI_ACC_INFO_METADATA( vm, acc_info, decl )                                                                                     \
     558             :     /* Translate the vaddr to the slice */                                                                                                         \
     559             :     fd_vm_vec_t const * FD_EXPAND_THEN_CONCAT2(decl, _hptr_) =                                                                                     \
     560             :       FD_VM_MEM_HADDR_LD( vm, vm_syscall_cpi_acc_info_rc_refcell_as_ptr( acc_info->data_box_addr ), FD_VM_RC_REFCELL_ALIGN, sizeof(fd_vm_vec_t) ); \
     561             :     /* Declare the vaddr of the slice's underlying byte array */                                                                                   \
     562             :     ulong FD_EXPAND_THEN_CONCAT2(decl, _vm_addr) = FD_EXPAND_THEN_CONCAT2(decl, _hptr_)->addr;                                                     \
     563             :     /* Declare the size of the slice's underlying byte array */                                                                                    \
     564             :     ulong FD_EXPAND_THEN_CONCAT2(decl, _len) = FD_EXPAND_THEN_CONCAT2(decl, _hptr_)->len;
     565             : 
     566             : #define VM_SYSCALL_CPI_ACC_INFO_LAMPORTS_RC_REFCELL_VADDR( vm, acc_info, decl ) \
     567           0 :     ulong decl = vm_syscall_cpi_acc_info_rc_refcell_as_ptr( acc_info->lamports_box_addr );
     568             : 
     569             : #define VM_SYSCALL_CPI_ACC_INFO_DATA_RC_REFCELL_VADDR( vm, acc_info, decl ) \
     570             :     ulong decl = vm_syscall_cpi_acc_info_rc_refcell_as_ptr( acc_info->data_box_addr );
     571             : 
     572             : #define VM_SYSCALL_CPI_SET_ACC_INFO_DATA_GET_LEN( vm, acc_info, decl ) \
     573           0 :   ulong FD_EXPAND_THEN_CONCAT2(decl, _len) = FD_EXPAND_THEN_CONCAT2(decl, _hptr_)->len;
     574             : 
     575             : #include "fd_vm_syscall_cpi_common.c"
     576             : 
     577             : #undef VM_SYSCALL_CPI_ABI
     578             : #undef VM_SYSCALL_CPI_INSTR_T
     579             : #undef VM_SYSCALL_CPI_INSTR_ALIGN
     580             : #undef VM_SYSCALL_CPI_INSTR_SIZE
     581             : #undef VM_SYSCALL_CPI_ACC_META_T
     582             : #undef VM_SYSCALL_CPI_ACC_META_ALIGN
     583             : #undef VM_SYSCALL_CPI_ACC_META_SIZE
     584             : #undef VM_SYSCALL_CPI_ACC_INFO_T
     585             : #undef VM_SYSCALL_CPI_ACC_INFO_ALIGN
     586             : #undef VM_SYSCALL_CPI_ACC_INFO_SIZE
     587             : #undef VM_SYSCALL_CPI_INSTR_DATA_ADDR
     588             : #undef VM_SYSCALL_CPI_INSTR_DATA_LEN
     589             : #undef VM_SYSCALL_CPI_INSTR_ACCS_ADDR
     590             : #undef VM_SYSCALL_CPI_INSTR_ACCS_LEN
     591             : #undef VM_SYSCALL_CPI_INSTR_PROGRAM_ID
     592             : #undef VM_SYSCALL_CPI_ACC_META_IS_WRITABLE
     593             : #undef VM_SYSCALL_CPI_ACC_META_IS_SIGNER
     594             : #undef VM_SYSCALL_CPI_ACC_META_PUBKEY
     595             : #undef VM_SYSCALL_CPI_ACC_INFO_LAMPORTS_VADDR
     596             : #undef VM_SYSCALL_CPI_ACC_INFO_LAMPORTS
     597             : #undef VM_SYSCALL_CPI_ACC_INFO_DATA_VADDR
     598             : #undef VM_SYSCALL_CPI_ACC_INFO_DATA
     599             : #undef VM_SYSCALL_CPI_ACC_INFO_DATA_LEN_VADDR
     600             : #undef VM_SYSCALL_CPI_ACC_INFO_METADATA
     601             : #undef VM_SYSCALL_CPI_ACC_INFO_LAMPORTS_RC_REFCELL_VADDR
     602             : #undef VM_SYSCALL_CPI_ACC_INFO_DATA_RC_REFCELL_VADDR
     603             : #undef VM_SYSCALL_CPI_SET_ACC_INFO_DATA_GET_LEN

Generated by: LCOV version 1.14