LCOV - code coverage report
Current view: top level - flamenco/runtime/sysvar - fd_sysvar_stake_history.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 97 111 87.4 %
Date: 2026-05-25 08:51:51 Functions: 5 5 100.0 %

          Line data    Source code
       1             : #include "fd_sysvar_stake_history.h"
       2             : #include "fd_sysvar.h"
       3             : #include "../fd_system_ids.h"
       4             : #include "../fd_accdb_svm.h"
       5             : #include "../../accdb/fd_accdb_sync.h"
       6             : #include "fd_sysvar_rent.h"
       7             : 
       8             : void
       9             : fd_sysvar_stake_history_init( fd_bank_t *               bank,
      10             :                               fd_accdb_user_t *         accdb,
      11             :                               fd_funk_txn_xid_t const * xid,
      12          24 :                               fd_capture_ctx_t *        capture_ctx ) {
      13          24 :   uchar data[ FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ ];
      14          24 :   fd_memset( data, 0, sizeof(data) );
      15          24 :   fd_sysvar_account_update( bank, accdb, xid, capture_ctx, &fd_sysvar_stake_history_id, data, FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ );
      16          24 : }
      17             : 
      18             : /* https://github.com/anza-xyz/agave/blob/v4.0.0-rc.1/runtime/src/bank.rs#L2452-L2463 */
      19             : 
      20             : void
      21             : fd_sysvar_stake_history_update( fd_bank_t *                      bank,
      22             :                                 fd_accdb_user_t *                accdb,
      23             :                                 fd_funk_txn_xid_t const *        xid,
      24             :                                 fd_capture_ctx_t *               capture_ctx,
      25         162 :                                 fd_stake_history_entry_t const * entry ) {
      26             : 
      27         162 :   fd_accdb_rw_t rw[1];
      28         162 :   fd_accdb_svm_update_t update[1];
      29         162 :   if( FD_UNLIKELY( !fd_accdb_svm_open_rw( accdb, bank, xid, rw, update, &fd_sysvar_stake_history_id, FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ, FD_ACCDB_FLAG_CREATE ) ) ) {
      30           0 :     FD_LOG_ERR(( "state is missing stake history sysvar" ));
      31           0 :   }
      32             : 
      33         162 :   if( FD_UNLIKELY( !fd_accdb_ref_lamports( rw->ro ) ) ) {
      34             :     /* Initialize account if it did not exist */
      35           0 :     fd_accdb_ref_owner_set( rw, &fd_sysvar_owner_id );
      36           0 :     fd_accdb_ref_lamports_set( rw, fd_rent_exempt_minimum_balance( &bank->f.rent, FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ ) );
      37           0 :     fd_accdb_ref_data_sz_set( accdb, rw, FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ, 0 );
      38             :     /* Now a valid StakeHistory sysvar with zero entries */
      39         162 :   } else {
      40             :     /* Sanity check existing state */
      41         162 :     if( FD_UNLIKELY( 0!=memcmp( fd_accdb_ref_owner( rw->ro ), &fd_sysvar_owner_id, sizeof(fd_pubkey_t) ) ) ) {
      42           0 :       FD_LOG_ERR(( "stake history sysvar not owned by sysvar owner" ));
      43           0 :     }
      44         162 :   }
      45             : 
      46         162 :   uchar * data    = fd_accdb_ref_data   ( rw );
      47         162 :   ulong   data_sz = fd_accdb_ref_data_sz( rw->ro );
      48         162 :   if( FD_UNLIKELY( data_sz < 8UL ) ) {
      49           0 :     FD_LOG_ERR(( "invalid stake history sysvar" ));
      50           0 :   }
      51             : 
      52         162 :   ulong len = FD_LOAD( ulong, data );
      53         162 :   len = fd_ulong_min( len, FD_SYSVAR_STAKE_HISTORY_CAP );
      54         162 :   ulong min_sz = 8UL + len * sizeof(fd_stake_history_entry_t);
      55         162 :   if( FD_UNLIKELY( data_sz < min_sz ) ) {
      56           0 :     FD_LOG_ERR(( "invalid stake history sysvar: data_sz too small (%lu, required %lu)", data_sz, min_sz ));
      57           0 :   }
      58             : 
      59             :   /* https://github.com/anza-xyz/solana-sdk/blob/account%40v4.3.0/account/src/lib.rs#L618 */
      60         162 :   if( data_sz!=FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ ) {
      61           6 :     fd_accdb_ref_data_sz_set( accdb, rw, FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ, 0 );
      62           6 :   }
      63             : 
      64         162 :   fd_stake_history_entry_t * entries = fd_type_pun( data+8UL );
      65             : 
      66             :   /* https://github.com/solana-program/stake/blob/interface%40v4.0.0/interface/src/stake_history.rs#L83 */
      67         162 :   ulong idx   = 0UL;
      68         162 :   int   found = 0;
      69         162 :   {
      70         162 :     ulong lo = 0UL;
      71         162 :     ulong hi = len;
      72         273 :     while( lo < hi ) {
      73         114 :       ulong mid         = lo + (hi - lo) / 2UL;
      74         114 :       ulong probe_epoch = entries[mid].epoch;
      75         114 :       if( entry->epoch == probe_epoch ) {
      76           3 :         idx   = mid;
      77           3 :         found = 1;
      78           3 :         break;
      79         111 :       } else if( entry->epoch > probe_epoch ) {
      80          78 :         hi = mid;
      81          78 :       } else {
      82          33 :         lo = mid + 1UL;
      83          33 :       }
      84         114 :     }
      85         162 :     if( !found ) idx = lo;
      86         162 :   }
      87             : 
      88             :   /* Ensure account is rent exempt
      89             :      https://github.com/anza-xyz/agave/blob/v4.0.0-rc.1/runtime/src/bank.rs#L5849-L5854 */
      90         162 :   ulong rent_min = fd_rent_exempt_minimum_balance( &bank->f.rent, FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ );
      91         162 :   if( rent_min > fd_accdb_ref_lamports( rw->ro ) ) {
      92           9 :     fd_accdb_ref_lamports_set( rw, rent_min );
      93           9 :   }
      94             : 
      95             :   /* Insert new element */
      96         162 :   ulong new_len = fd_ulong_if( found, len, fd_ulong_min( len+1UL, FD_SYSVAR_STAKE_HISTORY_CAP ) );
      97         162 :   ulong used_sz = 8UL + new_len * sizeof(fd_stake_history_entry_t);
      98             : 
      99         162 :   if( found ) {
     100             :     /* https://github.com/solana-program/stake/blob/interface%40v4.0.0/interface/src/stake_history.rs#L84 */
     101           3 :     entries[ idx ] = *entry;
     102         159 :   } else if( idx < FD_SYSVAR_STAKE_HISTORY_CAP ) {
     103             :     /* https://github.com/solana-program/stake/blob/interface%40v4.0.0/interface/src/stake_history.rs#L85
     104             :        https://github.com/solana-program/stake/blob/interface%40v4.0.0/interface/src/stake_history.rs#L87 */
     105         156 :     ulong shift_count = fd_ulong_min( len, FD_SYSVAR_STAKE_HISTORY_CAP-1UL ) - idx;
     106         156 :     memmove( &entries[ idx+1UL ], &entries[ idx ], shift_count * sizeof(fd_stake_history_entry_t) );
     107         156 :     entries[ idx ] = *entry;
     108         156 :     new_len = fd_ulong_min( len+1UL, FD_SYSVAR_STAKE_HISTORY_CAP );
     109         156 :   }
     110             :   /* else: idx == cap and not found - new entry would be truncated, drop */
     111             : 
     112             :   /* Zero trailing bytes (technically a no-op) */
     113         162 :   FD_TEST( used_sz <= FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ );
     114         162 :   fd_memset( data+used_sz, 0, FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ-used_sz );
     115             : 
     116         162 :   FD_STORE( ulong, data, new_len );
     117         162 :   fd_accdb_svm_close_rw( accdb, bank, capture_ctx, rw, update );
     118         162 : }
     119             : 
     120             : int
     121             : fd_sysvar_stake_history_validate( uchar const * data,
     122        7569 :                                   ulong         sz ) {
     123        7569 :   if( FD_UNLIKELY( sz < 8UL ) ) return 0;
     124        7569 :   ulong len = FD_LOAD( ulong, data );
     125        7569 :   ulong min_sz;
     126        7569 :   if( FD_UNLIKELY( __builtin_umull_overflow( len, 32UL, &min_sz ) ) ) return 0;
     127        7569 :   if( FD_UNLIKELY( __builtin_uaddl_overflow( min_sz, 8UL, &min_sz ) ) ) return 0;
     128        7569 :   if( FD_UNLIKELY( sz < min_sz ) ) return 0;
     129        7569 :   return 1;
     130        7569 : }
     131             : 
     132             : fd_stake_history_t *
     133             : fd_sysvar_stake_history_view( fd_stake_history_t * view,
     134             :                               uchar const *        data,
     135         606 :                               ulong                sz ) {
     136         606 :   if( FD_UNLIKELY( !fd_sysvar_stake_history_validate( data, sz ) ) ) return NULL;
     137         606 :   view->len     = FD_LOAD( ulong, data );
     138         606 :   view->entries = fd_type_pun_const( data + 8UL );
     139         606 :   return view;
     140         606 : }
     141             : 
     142             : fd_stake_history_entry_t const *
     143             : fd_sysvar_stake_history_query( fd_stake_history_t const * view,
     144         255 :                                ulong                      epoch ) {
     145         255 :   if( FD_UNLIKELY( !view || !view->len ) ) return NULL;
     146          66 :   if( epoch > view->entries[0].epoch ) return NULL;
     147             : 
     148          63 :   ulong off = view->entries[0].epoch - epoch;
     149          63 :   if( off < view->len && view->entries[off].epoch == epoch ) {
     150          24 :     return &view->entries[off];
     151          24 :   }
     152             : 
     153          39 :   ulong lo = 0UL;
     154          39 :   ulong hi = view->len - 1UL;
     155         165 :   while( lo <= hi ) {
     156         135 :     ulong mid = lo + ( hi - lo ) / 2UL;
     157         135 :     if( view->entries[mid].epoch == epoch ) {
     158           9 :       return &view->entries[mid];
     159         126 :     } else if( view->entries[mid].epoch > epoch ) {
     160         126 :       lo = mid + 1UL;
     161         126 :     } else {
     162           0 :       if( mid == 0UL ) return NULL;
     163           0 :       hi = mid - 1UL;
     164           0 :     }
     165         135 :   }
     166          30 :   return NULL;
     167          39 : }

Generated by: LCOV version 1.14