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