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