Line data Source code
1 : #ifndef HEADER_fd_src_flamenco_runtime_fd_account_h
2 : #define HEADER_fd_src_flamenco_runtime_fd_account_h
3 :
4 : /* fd_account.h contains safe API helpers for accounts.
5 :
6 : ### Account Existence
7 :
8 : "Does an account exist?" is not trivial to answer. We can instead
9 : look at qualifiers:
10 :
11 : A: Does a database record exist?
12 : B: Is the native (lamport) balance greater than zero?
13 : C: Is the account "dead"?
14 :
15 : From the on-chain program's perspective, even addresses that have
16 : never been interacted with can be read. That account would show up
17 : as zero balance, zero owner (system program), and has no data. This
18 : means, logically, for all possible 2^256 account addresses, it
19 : appears that an account exists. We call such accounts "dead"
20 : accounts (C).
21 :
22 : C also implies B. There are may be intermediate account states
23 : however where an account has zero balance, but is still owned by a
24 : program. Thus, B does not necessarily imply C.
25 :
26 : Obviously, we only have finite database space. Whenever an account
27 : becomes dead, we try to free the record. If no funk record for
28 : an account exists (C), it is dead (A). This doesn't always work, so
29 : sometimes there is a leftover record containing a dead account.
30 : (Also called a "tombstone")
31 :
32 : For the on-chain program developer this means: DO NOT assume a funk
33 : record exists for all accounts. DO NOT look at the funk record
34 : pointer, as its existence is U.B. for dead accounts.
35 :
36 : See fd_acc_exists and the below helper functions for safe use in
37 : native programs. */
38 :
39 : #include "fd_executor_err.h"
40 : #include "fd_system_ids.h"
41 : #include "context/fd_exec_epoch_ctx.h"
42 : #include "context/fd_exec_txn_ctx.h"
43 : #include "program/fd_program_util.h"
44 : #include "sysvar/fd_sysvar_rent.h"
45 : #include <assert.h> /* TODO remove */
46 :
47 : /* FD_ACC_SZ_MAX is the hardcoded size limit of a Solana account. */
48 :
49 0 : #define MAX_PERMITTED_DATA_LENGTH (10UL<<20) /* 10MiB */
50 : #define MAX_PERMITTED_ACCOUNT_DATA_ALLOCS_PER_TXN (10UL<<21) /* 20MiB */
51 :
52 : FD_PROTOTYPES_BEGIN
53 :
54 : /* Instruction account APIs *******************************************/
55 :
56 : /* Assert that enougha ccounts were supplied to this instruction. Returns
57 : FD_EXECUTOR_INSTR_SUCCESS if the number of accounts is as expected and
58 : FD_EXECUTOR_INSTR_ERR_NOT_ENOUGH_ACC_KEYS otherwise.
59 : https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/sdk/src/transaction_context.rs#L492-L503 */
60 : static inline int
61 : fd_account_check_num_insn_accounts( fd_exec_instr_ctx_t * ctx,
62 1977 : uint expected_accounts ) {
63 :
64 1977 : if( FD_UNLIKELY( ctx->instr->acct_cnt<expected_accounts ) ) {
65 0 : return FD_EXECUTOR_INSTR_ERR_NOT_ENOUGH_ACC_KEYS;
66 0 : }
67 1977 : return FD_EXECUTOR_INSTR_SUCCESS;
68 1977 : }
69 :
70 : /* fd_account_get_owner mirrors Anza function
71 : solana_sdk::transaction_context:Borrowed_account::get_owner. Returns 0
72 : iff the owner is retrieved successfully.
73 : https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/sdk/src/transaction_context.rs#L734-L738 */
74 : static inline fd_pubkey_t const *
75 : fd_account_get_owner( fd_exec_instr_ctx_t const * ctx,
76 0 : ulong instr_acc_idx ) {
77 0 : fd_borrowed_account_t * account = NULL;
78 0 : do {
79 0 : int err = fd_instr_borrowed_account_view_idx( ctx, (uchar)instr_acc_idx, &account );
80 0 : if( FD_UNLIKELY( err ) ) {
81 0 : FD_LOG_ERR(( "fd_instr_borrowed_account_view_idx failed (%d-%s)", err, fd_acc_mgr_strerror( err ) ));
82 0 : }
83 0 : } while(0);
84 0 :
85 0 : return (fd_pubkey_t const *) account->const_meta->info.owner;
86 0 : }
87 :
88 : /* fd_account_set_owner mirrors Anza function
89 : solana_sdk::transaction_context:Borrowed_account::set_owner. Returns 0
90 : iff the owner is set successfully. Acquires a writable handle. */
91 : int
92 : fd_account_set_owner( fd_exec_instr_ctx_t const * ctx,
93 : ulong instr_acc_idx,
94 : fd_pubkey_t const * owner );
95 :
96 : /* fd_account_get_lamports mirrors Anza function
97 : solana_sdk::transaction_context::BorrowedAccount::get_lamports.
98 : Returns current number of lamports in account. Well behaved if meta
99 : is NULL. */
100 :
101 : static inline ulong
102 0 : fd_account_get_lamports( fd_account_meta_t const * meta ) {
103 0 : if( FD_UNLIKELY( !meta ) ) return 0UL; /* (!meta) considered an internal error */
104 0 : return meta->info.lamports;
105 0 : }
106 :
107 : static inline ulong
108 : fd_account_get_lamports2( fd_exec_instr_ctx_t const * ctx,
109 27 : ulong instr_acc_idx ) {
110 27 : fd_borrowed_account_t * account = NULL;
111 27 : do {
112 27 : int err = fd_instr_borrowed_account_view_idx( ctx, (uchar)instr_acc_idx, &account );
113 27 : if( FD_UNLIKELY( err ) ) {
114 0 : return 0UL;
115 0 : }
116 27 : } while(0);
117 :
118 27 : return account->const_meta->info.lamports;
119 27 : }
120 :
121 : /* fd_account_set_lamports mirrors Anza function
122 : solana_sdk::transaction_context::BorrowedAccount::set_lamports.
123 : Runs through a sequence of permission checks, then sets the account
124 : balance. Does not update global capitalization. On success, returns
125 : 0 and updates meta->lamports. On failure, returns an
126 : FD_EXECUTOR_INSTR_ERR_{...} code. Acquires a writable handle. */
127 :
128 : int
129 : fd_account_set_lamports( fd_exec_instr_ctx_t const * ctx,
130 : ulong instr_acc_idx,
131 : ulong lamports );
132 :
133 : /*
134 : fd_account_checked_{add,sub}_lamports mirros Anza
135 : add/removes lamports to/from an
136 : account. Does not update global capitalization. Returns 0 on
137 : success or an FD_EXECUTOR_INSTR_ERR_{...} code on failure.
138 : Gracefully handles underflow. Acquires a writable handle.
139 : https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/sdk/src/transaction_context.rs#L798-L817 */
140 :
141 : static inline int
142 : fd_account_checked_add_lamports( fd_exec_instr_ctx_t const * ctx,
143 : ulong instr_acc_idx,
144 4254 : ulong lamports ) {
145 :
146 4254 : fd_borrowed_account_t * account = NULL;
147 4254 : do {
148 4254 : int err = fd_instr_borrowed_account_modify_idx( ctx, (uchar)instr_acc_idx, 0UL, &account );
149 4254 : if( FD_UNLIKELY( err ) ) {
150 0 : FD_LOG_ERR(( "fd_instr_borrowed_account_modify_idx failed (%d-%s)", err, fd_acc_mgr_strerror( err ) ));
151 0 : }
152 4254 : } while(0);
153 :
154 4254 : ulong balance_post = 0UL;
155 4254 : int err = fd_ulong_checked_add( account->const_meta->info.lamports, lamports, &balance_post );
156 4254 : if( FD_UNLIKELY( err ) ) {
157 216 : return FD_EXECUTOR_INSTR_ERR_ARITHMETIC_OVERFLOW;
158 216 : }
159 :
160 4038 : return fd_account_set_lamports( ctx, instr_acc_idx, balance_post );
161 4254 : }
162 :
163 : static inline int
164 : fd_account_checked_sub_lamports( fd_exec_instr_ctx_t const * ctx,
165 : ulong instr_acc_idx,
166 3867 : ulong lamports ) {
167 :
168 3867 : fd_borrowed_account_t * account = NULL;
169 3867 : do {
170 3867 : int err = fd_instr_borrowed_account_modify_idx( ctx, (uchar)instr_acc_idx, 0UL, &account );
171 3867 : if( FD_UNLIKELY( err ) ) {
172 0 : FD_LOG_ERR(( "fd_instr_borrowed_account_modify_idx failed (%d-%s)", err, fd_acc_mgr_strerror( err ) ));
173 0 : }
174 3867 : } while(0);
175 :
176 3867 : ulong balance_post = 0UL;
177 3867 : int err = fd_ulong_checked_sub( account->const_meta->info.lamports, lamports, &balance_post );
178 3867 : if( FD_UNLIKELY( err ) ) {
179 3 : return FD_EXECUTOR_INSTR_ERR_ARITHMETIC_OVERFLOW;
180 3 : }
181 :
182 3864 : return fd_account_set_lamports( ctx, instr_acc_idx, balance_post );
183 3867 : }
184 :
185 : /* fd_account_get_data_mut mirrors Anza function
186 : solana_sdk::transaction_context::BorrowedAccount::set_lamports.
187 : Returns a writable slice of the account data (transaction wide).
188 : Acquires a writable handle. This function assumes that the relevant
189 : borrowed has already acquired exclusive write access.
190 : https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/sdk/src/transaction_context.rs#L824-L831 */
191 : int
192 : fd_account_get_data_mut( fd_exec_instr_ctx_t const * ctx,
193 : ulong instr_acc_idx,
194 : uchar * * data_out,
195 : ulong * dlen_out );
196 :
197 : /* TODO: Implement fd_account_spare_data_capacity_mut which is used in direct mapping */
198 :
199 : /* fd_account_set_data_from_slice mirrors Anza function
200 : solana_sdk::transaction_context::BorrowedAccount::set_data_from_slice.
201 : In the firedancer client, it also mirrors the Anza function
202 : solana_sdk::transaction_context::BorrowedAccount::set_data.
203 : Assumes that destination account already has enough space to fit
204 : data. Acquires a writable handle.
205 : https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/sdk/src/transaction_context.rs#L867-882 */
206 :
207 : int
208 : fd_account_set_data_from_slice( fd_exec_instr_ctx_t const * ctx,
209 : ulong instr_acc_idx,
210 : uchar const * data,
211 : ulong data_sz );
212 :
213 : /* fd_account_set_data_length mirrors Anza function
214 : solana_sdk::transaction_context::BorrowedAccount::set_data_length.
215 : Acquires a writable handle. Returns 0 on success.
216 : https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/sdk/src/transaction_context.rs#L994-L900 */
217 :
218 : int
219 : fd_account_set_data_length( fd_exec_instr_ctx_t const * ctx,
220 : ulong instr_acc_idx,
221 : ulong new_len );
222 :
223 : /* fd_account_is_rent_exempt_at_data_length mirrors Anza function
224 : solana_sdk::transaction_context::BorrowedAccount::is_rent_exempt_at_data_length.
225 : Returns 1 if an account is rent exempt at it's current data length.
226 : https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/sdk/src/transaction_context.rs#L990-997 */
227 :
228 : static inline int
229 : fd_account_is_rent_exempt_at_data_length( fd_exec_instr_ctx_t const * ctx,
230 0 : fd_account_meta_t const * meta ) {
231 0 : assert( meta != NULL );
232 0 : fd_rent_t rent = ctx->epoch_ctx->epoch_bank.rent;
233 0 : ulong min_balance = fd_rent_exempt_minimum_balance( &rent, meta->dlen );
234 0 : return meta->info.lamports >= min_balance;
235 0 : }
236 :
237 : /* fd_account_is_executable returns 1 if the given account has the
238 : executable flag set. Otherwise, returns 0. Mirrors Anza's
239 : solana_sdk::transaction_context::BorrowedAccount::is_executable.
240 : https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/sdk/src/transaction_context.rs#L1001-1003 */
241 :
242 : FD_FN_PURE static inline int
243 38892 : fd_account_is_executable( fd_account_meta_t const * meta ) {
244 38892 : return !!meta->info.executable;
245 38892 : }
246 :
247 : /* fd_account_set_executable mirrors Anza function
248 : solana_sdk::transaction_context::BorrowedAccount::set_executable.
249 : Returns FD_EXECUTOR_INSTR_SUCCESS if the set is successful.
250 : https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/sdk/src/transaction_context.rs#L1007-1035 */
251 :
252 : int
253 : fd_account_set_executable( fd_exec_instr_ctx_t const * ctx,
254 : ulong instr_acc_idx,
255 : int is_executable );
256 :
257 : /* fd_account_get_rent_epoch mirrors Anza function
258 : solana_sdk::transaction_context::BorrowedAccount::get_rent_epoch.
259 : https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/sdk/src/transaction_context.rs#L1040-1042 */
260 :
261 : static inline ulong
262 0 : fd_account_get_rent_epoch( fd_account_meta_t const * meta ) {
263 0 : assert( meta != NULL );
264 0 : return meta->info.rent_epoch;
265 0 : }
266 :
267 : /* fd_account_is_{signer,writable} mirror the Anza functions
268 : solana_sdk::transaction_context::BorrowedAccount::is_{signer,writer}.
269 : Returns 1 if the account is a signer or is writable and 0 otherwise.
270 : https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/sdk/src/transaction_context.rs#L1044-1068 */
271 :
272 : static inline int
273 : fd_account_is_signer( fd_exec_instr_ctx_t const * ctx,
274 0 : ulong instr_acc_idx ) {
275 0 : if( FD_UNLIKELY( instr_acc_idx >= ctx->instr->acct_cnt ) ) {
276 0 : return 0;
277 0 : }
278 0 : return fd_instr_acc_is_signer_idx( ctx->instr, instr_acc_idx );
279 0 : }
280 :
281 : static inline int
282 : fd_account_is_writable( fd_exec_instr_ctx_t const * ctx,
283 0 : ulong instr_acc_idx ) {
284 0 : if( FD_UNLIKELY( instr_acc_idx >= ctx->instr->acct_cnt ) ) {
285 0 : return 0;
286 0 : }
287 0 :
288 0 : return fd_instr_acc_is_writable_idx( ctx->instr, instr_acc_idx );
289 0 : }
290 :
291 : /* fd_account_is_owned_by_current_program returns 1 if the given
292 : account is owned by the program invoked in the current instruction.
293 : Otherwise, returns 0. Mirrors Anza's
294 : solana_sdk::transaction_context::BorrowedAccount::is_owned_by_current_program */
295 :
296 : FD_FN_PURE static inline int
297 : fd_account_is_owned_by_current_program( fd_instr_info_t const * info,
298 28998 : fd_account_meta_t const * acct ) {
299 28998 : return 0==memcmp( info->program_id_pubkey.key, acct->info.owner, sizeof(fd_pubkey_t) );
300 28998 : }
301 :
302 : /* fd_account_can_data_be changed mirrors Anza function
303 : solana_sdk::transaction_context::BorrowedAccount::can_data_be_changed.
304 : https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/sdk/src/transaction_context.rs#L1078-L1094 */
305 : static inline int
306 : fd_account_can_data_be_changed( fd_instr_info_t const * instr,
307 : ulong instr_acc_idx,
308 14748 : int * err ) {
309 :
310 14748 : assert( instr_acc_idx < instr->acct_cnt );
311 14748 : fd_account_meta_t const * meta = instr->borrowed_accounts[ instr_acc_idx ]->const_meta;
312 :
313 14748 : if( FD_UNLIKELY( fd_account_is_executable( meta ) ) ) {
314 1314 : *err = FD_EXECUTOR_INSTR_ERR_EXECUTABLE_DATA_MODIFIED;
315 1314 : return 0;
316 1314 : }
317 :
318 13434 : if( FD_UNLIKELY( !fd_instr_acc_is_writable_idx( instr, instr_acc_idx ) ) ) {
319 984 : *err = FD_EXECUTOR_INSTR_ERR_READONLY_DATA_MODIFIED;
320 984 : return 0;
321 984 : }
322 :
323 12450 : if( FD_UNLIKELY( !fd_account_is_owned_by_current_program( instr, meta ) ) ) {
324 1236 : *err = FD_EXECUTOR_INSTR_ERR_EXTERNAL_DATA_MODIFIED;
325 1236 : return 0;
326 1236 : }
327 :
328 11214 : err = FD_EXECUTOR_INSTR_SUCCESS;
329 11214 : return 1;
330 12450 : }
331 :
332 : /* fd_account_can_data_be_resized mirrors Anza function
333 : solana_sdk::transaction_context::BorrowedAccount::can_data_be_resized
334 : https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/sdk/src/transaction_context.rs#L1096-L1119
335 : */
336 : static inline int
337 : fd_account_can_data_be_resized( fd_exec_instr_ctx_t const * instr_ctx,
338 : fd_account_meta_t const * acct,
339 : ulong new_length,
340 6072 : int * err ) {
341 : /* Only the owner can change the length of the data */
342 6072 : if( FD_UNLIKELY( ( acct->dlen != new_length ) &
343 6072 : ( !fd_account_is_owned_by_current_program( instr_ctx->instr, acct ) ) ) ) {
344 3 : *err = FD_EXECUTOR_INSTR_ERR_ACC_DATA_SIZE_CHANGED;
345 3 : return 0;
346 3 : }
347 :
348 : /* The new length can not exceed the maximum permitted length */
349 6069 : if( FD_UNLIKELY( new_length>MAX_PERMITTED_DATA_LENGTH ) ) {
350 0 : *err = FD_EXECUTOR_INSTR_ERR_INVALID_REALLOC;
351 0 : return 0;
352 0 : }
353 :
354 : /* The resize can not exceed the per-transaction maximum
355 : https://github.com/firedancer-io/agave/blob/1e460f466da60a63c7308e267c053eec41dc1b1c/sdk/src/transaction_context.rs#L1107-L1111 */
356 6069 : ulong length_delta = fd_ulong_sat_sub( new_length, acct->dlen );
357 6069 : ulong new_accounts_resize_delta = fd_ulong_sat_add( instr_ctx->txn_ctx->accounts_resize_delta, length_delta );
358 6069 : if( FD_UNLIKELY( new_accounts_resize_delta>MAX_PERMITTED_ACCOUNT_DATA_ALLOCS_PER_TXN ) ) {
359 0 : *err = FD_EXECUTOR_INSTR_ERR_MAX_ACCS_DATA_ALLOCS_EXCEEDED;
360 0 : return 0;
361 0 : }
362 :
363 6069 : *err = FD_EXECUTOR_INSTR_SUCCESS;
364 6069 : return 1;
365 6069 : }
366 :
367 : /* fd_account_update_acounts_resize_delta mirrors Anza function
368 : solana_sdk::transaction_context:BorrowedAccount::update_accounts_resize_delta.
369 : https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/sdk/src/transaction_context.rs#L1128-L1138 */
370 :
371 : int
372 : fd_account_update_accounts_resize_delta( fd_exec_instr_ctx_t const * ctx,
373 : ulong instr_acc_idx,
374 : ulong new_len,
375 : int * err );
376 :
377 : FD_FN_PURE static inline int
378 1974 : fd_account_is_zeroed( fd_account_meta_t const * acct ) {
379 : // TODO: optimize this
380 1974 : uchar const * data = ((uchar *) acct) + acct->hlen;
381 5790563646 : for( ulong i=0UL; i < acct->dlen; i++ )
382 5790561729 : if( data[i] != 0 )
383 57 : return 0;
384 1917 : return 1;
385 1974 : }
386 :
387 : /* fd_account_find_idx_of_insn_account returns the idx of the instruction account
388 : or -1 if the account is not found
389 : https://github.com/anza-xyz/agave/blob/d5a84daebd2a7225684aa3f722b330e9d5381e76/sdk/src/transaction_context.rs#L527
390 : */
391 : static inline int
392 : fd_account_find_idx_of_insn_account( fd_exec_instr_ctx_t const * ctx,
393 792 : fd_pubkey_t * pubkey ) {
394 5571 : for( ushort i=0; i<ctx->instr->acct_cnt; i++ ) {
395 5562 : if( 0==memcmp( pubkey, &ctx->instr->acct_pubkeys[i], sizeof(fd_pubkey_t) ) ) {
396 783 : return (int)i;
397 783 : }
398 5562 : }
399 9 : return -1;
400 792 : }
401 :
402 : /* Transaction account APIs *******************************************/
403 :
404 : /* https://github.com/anza-xyz/agave/blob/92ad51805862fbb47dc40968dff9f93b57395b51/sdk/program/src/message/legacy.rs#L636 */
405 : static inline int
406 133044 : fd_txn_account_is_writable_idx( fd_exec_txn_ctx_t const * txn_ctx, int idx ) {
407 133044 : int acct_addr_cnt = txn_ctx->txn_descriptor->acct_addr_cnt;
408 133044 : if( txn_ctx->txn_descriptor->transaction_version == FD_TXN_V0 ) {
409 126066 : acct_addr_cnt += txn_ctx->txn_descriptor->addr_table_adtl_cnt;
410 126066 : }
411 :
412 133044 : if( idx==acct_addr_cnt ) {
413 3 : return 0;
414 3 : }
415 :
416 133041 : if( fd_pubkey_is_active_reserved_key(&txn_ctx->accounts[idx] )
417 133041 : || ( FD_FEATURE_ACTIVE( txn_ctx->slot_ctx, add_new_reserved_account_keys ) && fd_pubkey_is_pending_reserved_key( &txn_ctx->accounts[idx] ) )) {
418 36462 : return 0;
419 36462 : }
420 :
421 96579 : if( fd_txn_account_is_demotion( txn_ctx, idx ) ) {
422 14055 : return 0;
423 14055 : }
424 :
425 82524 : return fd_txn_is_writable( txn_ctx->txn_descriptor, idx );
426 96579 : }
427 :
428 : FD_PROTOTYPES_END
429 :
430 : #include "fd_account_old.h"
431 :
432 : #endif /* HEADER_fd_src_flamenco_runtime_fd_account_h */
|