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 :
6 : void
7 : fd_sysvar_stake_history_init( fd_bank_t * bank,
8 : fd_accdb_t * accdb,
9 12 : fd_capture_ctx_t * capture_ctx ) {
10 12 : uchar data[ FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ ];
11 12 : fd_memset( data, 0, sizeof(data) );
12 12 : fd_sysvar_account_update( bank, accdb, capture_ctx, &fd_sysvar_stake_history_id, data, FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ );
13 12 : }
14 :
15 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-rc.1/runtime/src/bank.rs#L2452-L2463 */
16 :
17 : void
18 : fd_sysvar_stake_history_update( fd_bank_t * bank,
19 : fd_accdb_t * accdb,
20 : fd_capture_ctx_t * capture_ctx,
21 321 : fd_stake_history_entry_t const * entry ) {
22 321 : fd_accdb_svm_update_t update[1];
23 321 : fd_acc_t rw = fd_accdb_svm_open_rw( bank, accdb, update, &fd_sysvar_stake_history_id, FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ );
24 :
25 321 : if( FD_UNLIKELY( !rw.lamports ) ) {
26 : /* Initialize account if it did not exist */
27 3 : fd_memcpy( rw.owner, fd_sysvar_owner_id.key, sizeof(fd_pubkey_t) );
28 3 : rw.lamports = FD_SYSVAR_RENT_UNADJUSTED_INITIAL_BALANCE;
29 3 : rw.data_len = FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ;
30 3 : fd_memset( rw.data, 0, FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ );
31 : /* Now a valid StakeHistory sysvar with zero entries */
32 318 : } else {
33 : /* Sanity check existing state */
34 318 : if( FD_UNLIKELY( 0!=memcmp( rw.owner, &fd_sysvar_owner_id, sizeof(fd_pubkey_t) ) ) ) {
35 0 : FD_LOG_ERR(( "stake history sysvar not owned by sysvar owner" ));
36 0 : }
37 318 : }
38 :
39 321 : if( FD_UNLIKELY( rw.data_len<8UL ) ) FD_LOG_ERR(( "invalid stake history sysvar" ));
40 :
41 321 : ulong len = FD_LOAD( ulong, rw.data );
42 321 : len = fd_ulong_min( len, FD_SYSVAR_STAKE_HISTORY_CAP );
43 321 : ulong min_sz = 8UL + len * sizeof(fd_stake_history_entry_t);
44 321 : if( FD_UNLIKELY( rw.data_len<min_sz ) ) {
45 0 : FD_LOG_ERR(( "invalid stake history sysvar: data_len too small (%lu, required %lu)", rw.data_len, min_sz ));
46 0 : }
47 :
48 : /* https://github.com/anza-xyz/solana-sdk/blob/account%40v4.3.0/account/src/lib.rs#L618 */
49 321 : if( FD_UNLIKELY( rw.data_len!=FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ ) ) rw.data_len = FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ;
50 :
51 321 : fd_stake_history_entry_t * entries = fd_type_pun( rw.data+8UL );
52 :
53 : /* https://github.com/solana-program/stake/blob/interface%40v4.0.0/interface/src/stake_history.rs#L83 */
54 321 : ulong idx = 0UL;
55 321 : int found = 0;
56 321 : {
57 321 : ulong lo = 0UL;
58 321 : ulong hi = len;
59 474 : while( lo < hi ) {
60 159 : ulong mid = lo + (hi - lo) / 2UL;
61 159 : ulong probe_epoch = entries[mid].epoch;
62 159 : if( entry->epoch == probe_epoch ) {
63 6 : idx = mid;
64 6 : found = 1;
65 6 : break;
66 153 : } else if( entry->epoch > probe_epoch ) {
67 120 : hi = mid;
68 120 : } else {
69 33 : lo = mid + 1UL;
70 33 : }
71 159 : }
72 321 : if( !found ) idx = lo;
73 321 : }
74 :
75 : /* Insert new element */
76 321 : ulong new_len = fd_ulong_if( found, len, fd_ulong_min( len+1UL, FD_SYSVAR_STAKE_HISTORY_CAP ) );
77 321 : ulong used_sz = 8UL + new_len * sizeof(fd_stake_history_entry_t);
78 :
79 321 : if( found ) {
80 : /* https://github.com/solana-program/stake/blob/interface%40v4.0.0/interface/src/stake_history.rs#L84 */
81 6 : entries[ idx ] = *entry;
82 315 : } else if( idx < FD_SYSVAR_STAKE_HISTORY_CAP ) {
83 : /* https://github.com/solana-program/stake/blob/interface%40v4.0.0/interface/src/stake_history.rs#L85
84 : https://github.com/solana-program/stake/blob/interface%40v4.0.0/interface/src/stake_history.rs#L87 */
85 312 : ulong shift_count = fd_ulong_min( len, FD_SYSVAR_STAKE_HISTORY_CAP-1UL ) - idx;
86 312 : memmove( &entries[ idx+1UL ], &entries[ idx ], shift_count * sizeof(fd_stake_history_entry_t) );
87 312 : entries[ idx ] = *entry;
88 312 : new_len = fd_ulong_min( len+1UL, FD_SYSVAR_STAKE_HISTORY_CAP );
89 312 : }
90 : /* else: idx == cap and not found - new entry would be truncated, drop */
91 :
92 : /* Zero trailing bytes (technically a no-op) */
93 321 : FD_TEST( used_sz <= FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ );
94 321 : fd_memset( rw.data+used_sz, 0, FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ-used_sz );
95 :
96 321 : FD_STORE( ulong, rw.data, new_len );
97 :
98 : /* Balance is updated later by fd_stake_history_ensure_rent_exempt. */
99 321 : fd_accdb_svm_close_rw( bank, accdb, capture_ctx, &rw, update );
100 321 : }
101 :
102 : void
103 : fd_stake_history_ensure_rent_exempt( fd_bank_t * bank,
104 : fd_accdb_t * accdb,
105 291 : fd_capture_ctx_t * capture_ctx ) {
106 291 : fd_accdb_svm_update_t update[1];
107 291 : fd_acc_t rw = fd_accdb_svm_open_rw( bank, accdb, update, &fd_sysvar_stake_history_id, 0 );
108 291 : if( FD_UNLIKELY( !rw.lamports ) ) return; /* already released */
109 :
110 288 : fd_sysvar_adjust_balance_for_rent( bank, &rw );
111 288 : fd_accdb_svm_close_rw( bank, accdb, capture_ctx, &rw, update );
112 288 : }
113 :
114 : int
115 : fd_sysvar_stake_history_validate( uchar const * data,
116 9981 : ulong sz ) {
117 9981 : if( FD_UNLIKELY( sz < 8UL ) ) return 0;
118 9981 : ulong len = FD_LOAD( ulong, data );
119 9981 : ulong min_sz;
120 9981 : if( FD_UNLIKELY( __builtin_umull_overflow( len, 32UL, &min_sz ) ) ) return 0;
121 9981 : if( FD_UNLIKELY( __builtin_uaddl_overflow( min_sz, 8UL, &min_sz ) ) ) return 0;
122 9981 : if( FD_UNLIKELY( sz < min_sz ) ) return 0;
123 9981 : return 1;
124 9981 : }
125 :
126 : fd_stake_history_t *
127 : fd_sysvar_stake_history_view( fd_stake_history_t * view,
128 : uchar const * data,
129 1494 : ulong sz ) {
130 1494 : if( FD_UNLIKELY( !fd_sysvar_stake_history_validate( data, sz ) ) ) return NULL;
131 1494 : view->len = FD_LOAD( ulong, data );
132 1494 : view->entries = fd_type_pun_const( data + 8UL );
133 1494 : return view;
134 1494 : }
135 :
136 : fd_stake_history_entry_t const *
137 : fd_sysvar_stake_history_query( fd_stake_history_t const * view,
138 417 : ulong epoch ) {
139 417 : if( FD_UNLIKELY( !view || !view->len || epoch>view->entries[0].epoch ) ) return NULL;
140 180 : ulong index = view->entries[0].epoch - epoch;
141 180 : if( FD_UNLIKELY( index>=view->len ) ) return NULL;
142 45 : return &view->entries[index];
143 180 : }
144 :
145 : int
146 846 : fd_sysvar_stake_history_is_contiguous( fd_stake_history_t const * view ) {
147 846 : if( FD_UNLIKELY( !view || !view->len ) ) return 1;
148 636 : ulong newest = view->entries[0].epoch;
149 636 : if( FD_UNLIKELY( view->len-1UL>newest ) ) return 0;
150 1482 : for( ulong i=1UL; i<view->len; i++ ) {
151 846 : if( FD_UNLIKELY( view->entries[i].epoch!=newest-i ) ) return 0;
152 846 : }
153 636 : return 1;
154 636 : }
|