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 "fd_runtime.h"
42 : #include "context/fd_exec_epoch_ctx.h"
43 : #include "context/fd_exec_txn_ctx.h"
44 : #include "program/fd_program_util.h"
45 : #include "sysvar/fd_sysvar_rent.h"
46 : #include <assert.h> /* TODO remove */
47 :
48 : /* FD_ACC_SZ_MAX is the hardcoded size limit of a Solana account. */
49 :
50 0 : #define MAX_PERMITTED_DATA_LENGTH (10UL<<20) /* 10MiB */
51 : #define MAX_PERMITTED_ACCOUNT_DATA_ALLOCS_PER_TXN (10L<<21 ) /* 20MiB */
52 :
53 : /* Convenience macro for `fd_account_check_num_insn_accounts()` */
54 0 : #define CHECK_NUM_INSN_ACCS( _ctx, _expected ) do { \
55 0 : if( FD_UNLIKELY( (_ctx)->instr->acct_cnt<(_expected) ) ) { \
56 0 : return FD_EXECUTOR_INSTR_ERR_NOT_ENOUGH_ACC_KEYS; \
57 0 : } \
58 0 : } while(0)
59 :
60 : FD_PROTOTYPES_BEGIN
61 :
62 : /* Instruction account APIs *******************************************/
63 :
64 : /* FIXME: I don't like this way of accessing the account data... */
65 : static inline void *
66 0 : fd_account_get_data( fd_account_meta_t * m ) {
67 0 : return ((char *) m) + m->hlen;
68 0 : }
69 :
70 : /* Assert that enough ccounts were supplied to this instruction. Returns
71 : FD_EXECUTOR_INSTR_SUCCESS if the number of accounts is as expected and
72 : FD_EXECUTOR_INSTR_ERR_NOT_ENOUGH_ACC_KEYS otherwise.
73 : https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/sdk/src/transaction_context.rs#L492-L503 */
74 : static inline int
75 : fd_account_check_num_insn_accounts( fd_exec_instr_ctx_t * ctx,
76 0 : uint expected_accounts ) {
77 :
78 0 : if( FD_UNLIKELY( ctx->instr->acct_cnt<expected_accounts ) ) {
79 0 : return FD_EXECUTOR_INSTR_ERR_NOT_ENOUGH_ACC_KEYS;
80 0 : }
81 0 : return FD_EXECUTOR_INSTR_SUCCESS;
82 0 : }
83 :
84 : /* fd_account_get_owner mirrors Anza function
85 : solana_sdk::transaction_context:Borrowed_account::get_owner. Returns 0
86 : iff the owner is retrieved successfully.
87 : https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/sdk/src/transaction_context.rs#L734-L738 */
88 : static inline fd_pubkey_t const *
89 : fd_account_get_owner( fd_exec_instr_ctx_t const * ctx,
90 0 : ulong instr_acc_idx ) {
91 0 : fd_borrowed_account_t * account = NULL;
92 0 : do {
93 0 : int err = fd_instr_borrowed_account_view_idx( ctx, (uchar)instr_acc_idx, &account );
94 0 : if( FD_UNLIKELY( err ) ) {
95 0 : FD_LOG_ERR(( "fd_instr_borrowed_account_view_idx failed (%d-%s)", err, fd_acc_mgr_strerror( err ) ));
96 0 : }
97 0 : } while(0);
98 0 :
99 0 : return (fd_pubkey_t const *) account->const_meta->info.owner;
100 0 : }
101 :
102 : /* fd_account_set_owner mirrors Anza function
103 : solana_sdk::transaction_context:Borrowed_account::set_owner. Returns 0
104 : iff the owner is set successfully. Acquires a writable handle. */
105 : int
106 : fd_account_set_owner( fd_exec_instr_ctx_t const * ctx,
107 : ulong instr_acc_idx,
108 : fd_pubkey_t const * owner );
109 :
110 : /* fd_account_get_lamports mirrors Anza function
111 : solana_sdk::transaction_context::BorrowedAccount::get_lamports.
112 : Returns current number of lamports in account. Well behaved if meta
113 : is NULL. */
114 :
115 : static inline ulong
116 0 : fd_account_get_lamports( fd_account_meta_t const * meta ) {
117 0 : if( FD_UNLIKELY( !meta ) ) return 0UL; /* (!meta) considered an internal error */
118 0 : return meta->info.lamports;
119 0 : }
120 :
121 : static inline ulong
122 : fd_account_get_lamports2( fd_exec_instr_ctx_t const * ctx,
123 0 : ulong instr_acc_idx ) {
124 0 : fd_borrowed_account_t * account = NULL;
125 0 : do {
126 0 : int err = fd_instr_borrowed_account_view_idx( ctx, (uchar)instr_acc_idx, &account );
127 0 : if( FD_UNLIKELY( err ) ) {
128 0 : return 0UL;
129 0 : }
130 0 : } while(0);
131 :
132 0 : return account->const_meta->info.lamports;
133 0 : }
134 :
135 : /* fd_account_set_lamports mirrors Anza function
136 : solana_sdk::transaction_context::BorrowedAccount::set_lamports.
137 : Runs through a sequence of permission checks, then sets the account
138 : balance. Does not update global capitalization. On success, returns
139 : 0 and updates meta->lamports. On failure, returns an
140 : FD_EXECUTOR_INSTR_ERR_{...} code. Acquires a writable handle. */
141 :
142 : int
143 : fd_account_set_lamports( fd_exec_instr_ctx_t const * ctx,
144 : ulong instr_acc_idx,
145 : ulong lamports );
146 :
147 : /*
148 : fd_account_checked_{add,sub}_lamports mirros Anza
149 : add/removes lamports to/from an
150 : account. Does not update global capitalization. Returns 0 on
151 : success or an FD_EXECUTOR_INSTR_ERR_{...} code on failure.
152 : Gracefully handles underflow. Acquires a writable handle.
153 : https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/sdk/src/transaction_context.rs#L798-L817 */
154 :
155 : static inline int
156 : fd_account_checked_add_lamports( fd_exec_instr_ctx_t const * ctx,
157 : ulong instr_acc_idx,
158 0 : ulong lamports ) {
159 :
160 0 : fd_borrowed_account_t * account = NULL;
161 0 : do {
162 0 : int err = fd_instr_borrowed_account_modify_idx( ctx, (uchar)instr_acc_idx, 0UL, &account );
163 0 : if( FD_UNLIKELY( err ) ) {
164 0 : FD_LOG_ERR(( "fd_instr_borrowed_account_modify_idx failed (%d-%s)", err, fd_acc_mgr_strerror( err ) ));
165 0 : }
166 0 : } while(0);
167 :
168 0 : ulong balance_post = 0UL;
169 0 : int err = fd_ulong_checked_add( account->const_meta->info.lamports, lamports, &balance_post );
170 0 : if( FD_UNLIKELY( err ) ) {
171 0 : return FD_EXECUTOR_INSTR_ERR_ARITHMETIC_OVERFLOW;
172 0 : }
173 :
174 0 : return fd_account_set_lamports( ctx, instr_acc_idx, balance_post );
175 0 : }
176 :
177 : static inline int
178 : fd_account_checked_sub_lamports( fd_exec_instr_ctx_t const * ctx,
179 : ulong instr_acc_idx,
180 0 : ulong lamports ) {
181 :
182 0 : fd_borrowed_account_t * account = NULL;
183 0 : do {
184 0 : int err = fd_instr_borrowed_account_modify_idx( ctx, (uchar)instr_acc_idx, 0UL, &account );
185 0 : if( FD_UNLIKELY( err ) ) {
186 0 : FD_LOG_ERR(( "fd_instr_borrowed_account_modify_idx failed (%d-%s)", err, fd_acc_mgr_strerror( err ) ));
187 0 : }
188 0 : } while(0);
189 :
190 0 : ulong balance_post = 0UL;
191 0 : int err = fd_ulong_checked_sub( account->const_meta->info.lamports, lamports, &balance_post );
192 0 : if( FD_UNLIKELY( err ) ) {
193 0 : return FD_EXECUTOR_INSTR_ERR_ARITHMETIC_OVERFLOW;
194 0 : }
195 :
196 0 : return fd_account_set_lamports( ctx, instr_acc_idx, balance_post );
197 0 : }
198 :
199 : /* fd_account_get_data_mut mirrors Anza function
200 : solana_sdk::transaction_context::BorrowedAccount::set_lamports.
201 : Returns a writable slice of the account data (transaction wide).
202 : Acquires a writable handle. This function assumes that the relevant
203 : borrowed has already acquired exclusive write access.
204 : https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/sdk/src/transaction_context.rs#L824-L831 */
205 : int
206 : fd_account_get_data_mut( fd_exec_instr_ctx_t const * ctx,
207 : ulong instr_acc_idx,
208 : uchar * * data_out,
209 : ulong * dlen_out );
210 :
211 : /* TODO: Implement fd_account_spare_data_capacity_mut which is used in direct mapping */
212 :
213 : /* fd_account_set_data_from_slice mirrors Anza function
214 : solana_sdk::transaction_context::BorrowedAccount::set_data_from_slice.
215 : In the firedancer client, it also mirrors the Anza function
216 : solana_sdk::transaction_context::BorrowedAccount::set_data.
217 : Assumes that destination account already has enough space to fit
218 : data. Acquires a writable handle.
219 : https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/sdk/src/transaction_context.rs#L867-882 */
220 :
221 : int
222 : fd_account_set_data_from_slice( fd_exec_instr_ctx_t const * ctx,
223 : ulong instr_acc_idx,
224 : uchar const * data,
225 : ulong data_sz );
226 :
227 : /* fd_account_set_data_length mirrors Anza function
228 : solana_sdk::transaction_context::BorrowedAccount::set_data_length.
229 : Acquires a writable handle. Returns 0 on success.
230 : https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/sdk/src/transaction_context.rs#L994-L900 */
231 :
232 : int
233 : fd_account_set_data_length( fd_exec_instr_ctx_t const * ctx,
234 : ulong instr_acc_idx,
235 : ulong new_len );
236 :
237 : /* fd_account_is_rent_exempt_at_data_length mirrors Anza function
238 : solana_sdk::transaction_context::BorrowedAccount::is_rent_exempt_at_data_length.
239 : Returns 1 if an account is rent exempt at it's current data length.
240 : https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/sdk/src/transaction_context.rs#L990-997 */
241 :
242 : static inline int
243 : fd_account_is_rent_exempt_at_data_length( fd_exec_instr_ctx_t const * ctx,
244 0 : fd_account_meta_t const * meta ) {
245 0 : assert( meta != NULL );
246 0 : fd_rent_t rent = ctx->epoch_ctx->epoch_bank.rent;
247 0 : ulong min_balance = fd_rent_exempt_minimum_balance( &rent, meta->dlen );
248 0 : return meta->info.lamports >= min_balance;
249 0 : }
250 :
251 : /* fd_account_is_executable returns 1 if the given account has the
252 : executable flag set. Otherwise, returns 0. Mirrors Anza's
253 : solana_sdk::transaction_context::BorrowedAccount::is_executable.
254 : https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/sdk/src/transaction_context.rs#L1001-1003 */
255 :
256 : FD_FN_PURE static inline int
257 0 : fd_account_is_executable( fd_account_meta_t const * meta ) {
258 0 : return !!meta->info.executable;
259 0 : }
260 :
261 : /* fd_account_is_executable_internal was introduced to move towards deprecating the `is_executable` flag.
262 : It returns true if the `remove_accounts_executable_flag_checks` feature is inactive AND fd_account_is_executable
263 : return true. This is newly used in account modification logic to eventually allow "executable" accounts to be
264 : modified.
265 : https://github.com/anza-xyz/agave/blob/89872fdb074e6658646b2b57a299984f0059cc84/sdk/transaction-context/src/lib.rs#L1052-L1060 */
266 :
267 : FD_FN_PURE static inline int
268 : fd_account_is_executable_internal( fd_exec_slot_ctx_t const * slot_ctx,
269 0 : fd_account_meta_t const * meta ) {
270 0 : return !FD_FEATURE_ACTIVE( slot_ctx, remove_accounts_executable_flag_checks ) &&
271 0 : fd_account_is_executable( meta );
272 0 : }
273 :
274 : /* fd_account_set_executable mirrors Anza function
275 : solana_sdk::transaction_context::BorrowedAccount::set_executable.
276 : Returns FD_EXECUTOR_INSTR_SUCCESS if the set is successful.
277 : https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/sdk/src/transaction_context.rs#L1007-1035 */
278 :
279 : int
280 : fd_account_set_executable( fd_exec_instr_ctx_t const * ctx,
281 : ulong instr_acc_idx,
282 : int is_executable );
283 :
284 : /* fd_account_get_rent_epoch mirrors Anza function
285 : solana_sdk::transaction_context::BorrowedAccount::get_rent_epoch.
286 : https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/sdk/src/transaction_context.rs#L1040-1042 */
287 :
288 : static inline ulong
289 0 : fd_account_get_rent_epoch( fd_account_meta_t const * meta ) {
290 0 : assert( meta != NULL );
291 0 : return meta->info.rent_epoch;
292 0 : }
293 :
294 : /* fd_account_is_{signer,writable} mirror the Anza functions
295 : solana_sdk::transaction_context::BorrowedAccount::is_{signer,writer}.
296 : Returns 1 if the account is a signer or is writable and 0 otherwise.
297 : https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/sdk/src/transaction_context.rs#L1044-1068 */
298 :
299 : static inline int
300 : fd_account_is_signer( fd_exec_instr_ctx_t const * ctx,
301 0 : ulong instr_acc_idx ) {
302 0 : if( FD_UNLIKELY( instr_acc_idx >= ctx->instr->acct_cnt ) ) {
303 0 : return 0;
304 0 : }
305 0 : return fd_instr_acc_is_signer_idx( ctx->instr, instr_acc_idx );
306 0 : }
307 :
308 : static inline int
309 : fd_account_is_writable( fd_exec_instr_ctx_t const * ctx,
310 0 : ulong instr_acc_idx ) {
311 0 : if( FD_UNLIKELY( instr_acc_idx >= ctx->instr->acct_cnt ) ) {
312 0 : return 0;
313 0 : }
314 0 :
315 0 : return fd_instr_acc_is_writable_idx( ctx->instr, instr_acc_idx );
316 0 : }
317 :
318 : /* fd_account_is_owned_by_current_program returns 1 if the given
319 : account is owned by the program invoked in the current instruction.
320 : Otherwise, returns 0. Mirrors Anza's
321 : solana_sdk::transaction_context::BorrowedAccount::is_owned_by_current_program */
322 :
323 : FD_FN_PURE static inline int
324 : fd_account_is_owned_by_current_program( fd_instr_info_t const * info,
325 0 : fd_account_meta_t const * acct ) {
326 0 : return 0==memcmp( info->program_id_pubkey.key, acct->info.owner, sizeof(fd_pubkey_t) );
327 0 : }
328 :
329 : /* fd_account_can_data_be changed mirrors Anza function
330 : solana_sdk::transaction_context::BorrowedAccount::can_data_be_changed.
331 : https://github.com/anza-xyz/agave/blob/89872fdb074e6658646b2b57a299984f0059cc84/sdk/transaction-context/src/lib.rs#L1136-L1152 */
332 : static inline int
333 : fd_account_can_data_be_changed( fd_exec_instr_ctx_t const * ctx,
334 : ulong instr_acc_idx,
335 0 : int * err ) {
336 :
337 0 : fd_instr_info_t const * instr = ctx->instr;
338 0 : assert( instr_acc_idx < instr->acct_cnt );
339 0 : fd_account_meta_t const * meta = instr->borrowed_accounts[ instr_acc_idx ]->const_meta;
340 :
341 0 : if( FD_UNLIKELY( fd_account_is_executable_internal( ctx->slot_ctx, meta ) ) ) {
342 0 : *err = FD_EXECUTOR_INSTR_ERR_EXECUTABLE_DATA_MODIFIED;
343 0 : return 0;
344 0 : }
345 :
346 0 : if( FD_UNLIKELY( !fd_instr_acc_is_writable_idx( instr, instr_acc_idx ) ) ) {
347 0 : *err = FD_EXECUTOR_INSTR_ERR_READONLY_DATA_MODIFIED;
348 0 : return 0;
349 0 : }
350 :
351 0 : if( FD_UNLIKELY( !fd_account_is_owned_by_current_program( instr, meta ) ) ) {
352 0 : *err = FD_EXECUTOR_INSTR_ERR_EXTERNAL_DATA_MODIFIED;
353 0 : return 0;
354 0 : }
355 :
356 0 : *err = FD_EXECUTOR_INSTR_SUCCESS;
357 0 : return 1;
358 0 : }
359 :
360 : /* fd_account_can_data_be_resized mirrors Anza function
361 : solana_sdk::transaction_context::BorrowedAccount::can_data_be_resized
362 : https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/sdk/src/transaction_context.rs#L1096-L1119
363 : */
364 : static inline int
365 : fd_account_can_data_be_resized( fd_exec_instr_ctx_t const * instr_ctx,
366 : fd_account_meta_t const * acct,
367 : ulong new_length,
368 0 : int * err ) {
369 : /* Only the owner can change the length of the data */
370 0 : if( FD_UNLIKELY( ( acct->dlen != new_length ) &
371 0 : ( !fd_account_is_owned_by_current_program( instr_ctx->instr, acct ) ) ) ) {
372 0 : *err = FD_EXECUTOR_INSTR_ERR_ACC_DATA_SIZE_CHANGED;
373 0 : return 0;
374 0 : }
375 :
376 : /* The new length can not exceed the maximum permitted length */
377 0 : if( FD_UNLIKELY( new_length>MAX_PERMITTED_DATA_LENGTH ) ) {
378 0 : *err = FD_EXECUTOR_INSTR_ERR_INVALID_REALLOC;
379 0 : return 0;
380 0 : }
381 :
382 : /* The resize can not exceed the per-transaction maximum
383 : https://github.com/firedancer-io/agave/blob/1e460f466da60a63c7308e267c053eec41dc1b1c/sdk/src/transaction_context.rs#L1107-L1111 */
384 0 : long length_delta = fd_long_sat_sub( (long)new_length, (long)acct->dlen );
385 0 : long new_accounts_resize_delta = fd_long_sat_add( (long)instr_ctx->txn_ctx->accounts_resize_delta, length_delta );
386 0 : if( FD_UNLIKELY( new_accounts_resize_delta>MAX_PERMITTED_ACCOUNT_DATA_ALLOCS_PER_TXN ) ) {
387 0 : *err = FD_EXECUTOR_INSTR_ERR_MAX_ACCS_DATA_ALLOCS_EXCEEDED;
388 0 : return 0;
389 0 : }
390 :
391 0 : *err = FD_EXECUTOR_INSTR_SUCCESS;
392 0 : return 1;
393 0 : }
394 :
395 : /* fd_account_update_acounts_resize_delta mirrors Anza function
396 : solana_sdk::transaction_context:BorrowedAccount::update_accounts_resize_delta.
397 : https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/sdk/src/transaction_context.rs#L1128-L1138 */
398 :
399 : int
400 : fd_account_update_accounts_resize_delta( fd_exec_instr_ctx_t const * ctx,
401 : ulong instr_acc_idx,
402 : ulong new_len,
403 : int * err );
404 :
405 : FD_FN_PURE static inline int
406 0 : fd_account_is_zeroed( fd_account_meta_t const * acct ) {
407 : // TODO: optimize this
408 0 : uchar const * data = ((uchar *) acct) + acct->hlen;
409 0 : for( ulong i=0UL; i < acct->dlen; i++ )
410 0 : if( data[i] != 0 )
411 0 : return 0;
412 0 : return 1;
413 0 : }
414 :
415 : /* fd_account_find_idx_of_insn_account returns the idx of the instruction account
416 : or -1 if the account is not found
417 : https://github.com/anza-xyz/agave/blob/d5a84daebd2a7225684aa3f722b330e9d5381e76/sdk/src/transaction_context.rs#L527
418 : */
419 : static inline int
420 : fd_account_find_idx_of_insn_account( fd_exec_instr_ctx_t const * ctx,
421 0 : fd_pubkey_t * pubkey ) {
422 0 : for( ushort i=0; i<ctx->instr->acct_cnt; i++ ) {
423 0 : if( 0==memcmp( pubkey, &ctx->instr->acct_pubkeys[i], sizeof(fd_pubkey_t) ) ) {
424 0 : return (int)i;
425 0 : }
426 0 : }
427 0 : return -1;
428 0 : }
429 :
430 : /* Backward scan over transaction accounts
431 : Returns -1 if not found
432 : https://github.com/anza-xyz/agave/blob/228d3b3427abcf3ecfe9757bfa5de411fc020773/sdk/src/transaction_context.rs#L249
433 : */
434 : static inline int
435 : fd_account_find_index_of_program_account( fd_exec_txn_ctx_t const * ctx,
436 0 : fd_pubkey_t * pubkey ) {
437 0 : for( ulong i=ctx->accounts_cnt; i>0UL; i-- ) {
438 0 : if( 0==memcmp( pubkey, &ctx->accounts[ i-1UL ], sizeof(fd_pubkey_t) ) ) {
439 0 : return (int)((ushort)i);
440 0 : }
441 0 : }
442 0 : return -1;
443 0 : }
444 :
445 : FD_PROTOTYPES_END
446 :
447 : #endif /* HEADER_fd_src_flamenco_runtime_fd_account_h */
|