Line data Source code
1 : #ifndef HEADER_fd_src_flamenco_runtime_fd_borrowed_account_h 2 : #define HEADER_fd_src_flamenco_runtime_fd_borrowed_account_h 3 : 4 : #include "fd_executor_err.h" 5 : #include "fd_system_ids.h" 6 : #include "fd_runtime.h" 7 : #include "context/fd_exec_epoch_ctx.h" 8 : #include "context/fd_exec_txn_ctx.h" 9 : #include "sysvar/fd_sysvar_rent.h" 10 : 11 : /* FD_ACC_SZ_MAX is the hardcoded size limit of a Solana account. */ 12 : 13 0 : #define MAX_PERMITTED_DATA_LENGTH (10UL<<20) /* 10MiB */ 14 : #define MAX_PERMITTED_ACCOUNT_DATA_ALLOCS_PER_TXN (10L<<21 ) /* 20MiB */ 15 : 16 : /* TODO: Not all Agave Borrowed Account API functions are implemented here */ 17 : 18 : struct fd_borrowed_account { 19 : ulong magic; 20 : fd_txn_account_t * acct; 21 : fd_exec_instr_ctx_t const * instr_ctx; 22 : int instr_acc_idx; 23 : }; 24 : typedef struct fd_borrowed_account fd_borrowed_account_t; 25 : 26 0 : #define FD_BORROWED_ACCOUNT_MAGIC (0xFDB07703ACC736C0UL) /* FD BORROW ACCT MGC version 0 */ 27 : /* prevents borrowed accounts from going out of scope without releasing a borrow */ 28 0 : #define fd_guarded_borrowed_account_t __attribute__((cleanup(fd_borrowed_account_destroy))) fd_borrowed_account_t 29 : 30 : FD_PROTOTYPES_BEGIN 31 : 32 : /* Constructor */ 33 : 34 : static inline void 35 : fd_borrowed_account_init( fd_borrowed_account_t * borrowed_acct, 36 : fd_txn_account_t * acct, 37 : fd_exec_instr_ctx_t const * instr_ctx, 38 0 : int instr_acc_idx ) { 39 0 : borrowed_acct->acct = acct; 40 0 : borrowed_acct->instr_ctx = instr_ctx; 41 0 : borrowed_acct->instr_acc_idx = instr_acc_idx; 42 : 43 0 : FD_COMPILER_MFENCE(); 44 0 : borrowed_acct->magic = FD_BORROWED_ACCOUNT_MAGIC; 45 0 : FD_COMPILER_MFENCE(); 46 0 : } 47 : 48 : /* Drop mirrors the behavior of rust's std::mem::drop on mutable borrows. 49 : Releases the acquired write on the borrowed account object. */ 50 : 51 : static inline void 52 0 : fd_borrowed_account_drop( fd_borrowed_account_t * borrowed_acct ) { 53 0 : fd_txn_account_release_write_private( borrowed_acct->acct ); 54 0 : } 55 : 56 : /* Destructor */ 57 : 58 : static inline void 59 0 : fd_borrowed_account_destroy( fd_borrowed_account_t * borrowed_acct ) { 60 0 : if( FD_LIKELY( borrowed_acct->magic == FD_BORROWED_ACCOUNT_MAGIC ) ) { 61 0 : fd_txn_account_release_write_private( borrowed_acct->acct ); 62 0 : } 63 : 64 0 : FD_COMPILER_MFENCE(); 65 0 : FD_VOLATILE( borrowed_acct->magic ) = 0UL; 66 0 : FD_COMPILER_MFENCE(); 67 : 68 0 : borrowed_acct = NULL; 69 0 : } 70 : 71 : /* Getters */ 72 : 73 : /* fd_borrowed_account_get_data mirrors Agave function 74 : solana_sdk::transaction_context::BorrowedAccount::get_data. 75 : 76 : https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L817 */ 77 : 78 : static inline void const * 79 0 : fd_borrowed_account_get_data( fd_borrowed_account_t const * borrowed_acct ) { 80 0 : return borrowed_acct->acct->const_data; 81 0 : } 82 : 83 : /* fd_borrowed_account_get_data_mut mirrors Agave function 84 : solana_sdk::transaction_context::BorrowedAccount::get_data_mut. 85 : 86 : Returns a writable slice of the account data (transaction wide). 87 : Acquires a writable handle. This function assumes that the relevant 88 : borrowed has already acquired exclusive write access. 89 : 90 : https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L823 */ 91 : 92 : int 93 : fd_borrowed_account_get_data_mut( fd_borrowed_account_t * borrowed_acct, 94 : uchar * * data_out, 95 : ulong * dlen_out ); 96 : 97 : static inline fd_pubkey_t const * 98 0 : fd_borrowed_account_get_owner( fd_borrowed_account_t * borrowed_acct ) { 99 0 : return (fd_pubkey_t const *) borrowed_acct->acct->const_meta->info.owner; 100 0 : } 101 : 102 : /* fd_borrowed_account_get_lamports mirrors Agave function 103 : solana_sdk::transaction_context::BorrowedAccount::get_lamports. 104 : 105 : Returns current number of lamports in account. Well behaved if meta 106 : is NULL. 107 : 108 : https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L767 */ 109 : 110 : static inline ulong 111 0 : fd_borrowed_account_get_lamports( fd_borrowed_account_t const * borrowed_acct ) { 112 0 : return fd_txn_account_get_lamports( borrowed_acct->acct ); 113 0 : } 114 : 115 : /* fd_borrowed_account_get_rent_epoch mirrors Agave function 116 : solana_sdk::transaction_context::BorrowedAccount::get_rent_epoch. 117 : 118 : https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L1034 */ 119 : 120 : static inline ulong 121 0 : fd_borrowed_account_get_rent_epoch( fd_borrowed_account_t const * borrowed_acct ) { 122 0 : FD_TEST( borrowed_acct->acct->const_meta != NULL ); 123 0 : /* TODO: rent_epoch should be a property of fd_txn_account_t */ 124 0 : return borrowed_acct->acct->const_meta->info.rent_epoch; 125 0 : } 126 : 127 : /* Setters */ 128 : 129 : /* fd_borrowed_account_set_owner mirrors Agave function 130 : solana_sdk::transaction_context::BorrowedAccount::set_owner. 131 : 132 : https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L739 */ 133 : 134 : int 135 : fd_borrowed_account_set_owner( fd_borrowed_account_t * borrowed_acct, 136 : fd_pubkey_t const * owner ); 137 : 138 : /* fd_borrowed_account_set_lamports mirrors Agave function 139 : solana_sdk::transaction_context::BorrowedAccount::set_lamports. 140 : 141 : Runs through a sequence of permission checks, then sets the account 142 : balance. Does not update global capitalization. On success, returns 143 : 0 and updates meta->lamports. On failure, returns an 144 : FD_EXECUTOR_INSTR_ERR_{...} code. Acquires a writable handle. 145 : 146 : https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L773 */ 147 : 148 : int 149 : fd_borrowed_account_set_lamports( fd_borrowed_account_t * borrowed_acct, 150 : ulong lamports ); 151 : 152 : /* fd_borrowed_account_set_data_from_slice mirrors Agave function 153 : solana_sdk::transaction_context::BorrowedAccount::set_data_from_slice. 154 : 155 : In the firedancer client, it also mirrors the Agave function 156 : solana_sdk::transaction_context::BorrowedAccount::set_data. 157 : Assumes that destination account already has enough space to fit 158 : data. Acquires a writable handle. 159 : 160 : https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L864 */ 161 : 162 : int 163 : fd_borrowed_account_set_data_from_slice( fd_borrowed_account_t * borrowed_acct, 164 : uchar const * data, 165 : ulong data_sz ); 166 : 167 : /* fd_borrowed_account_set_data_length mirrors Agave function 168 : solana_sdk::transaction_context::BorrowedAccount::set_data_length. 169 : 170 : Acquires a writable handle. Returns 0 on success. 171 : https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L882 */ 172 : 173 : int 174 : fd_borrowed_account_set_data_length( fd_borrowed_account_t * borrowed_acct, 175 : ulong new_len ); 176 : 177 : /* fd_borrowed_account_set_executable mirrors Agave function 178 : solana_sdk::transaction_context::BorrowedAccount::set_executable. 179 : 180 : Returns FD_EXECUTOR_INSTR_SUCCESS if the set is successful. 181 : 182 : https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L10015 */ 183 : 184 : int 185 : fd_borrowed_account_set_executable( fd_borrowed_account_t * borrowed_acct, 186 : int is_executable ); 187 : 188 : /* Operators */ 189 : 190 : /* fd_borrowed_account_checked_add_lamports mirros Agave function 191 : solana_sdk::transaction_context::BorrowedAccount::checked_add_lamports. 192 : 193 : Does not update global capitalization. Returns 0 on 194 : success or an FD_EXECUTOR_INSTR_ERR_{...} code on failure. 195 : Gracefully handles underflow. 196 : 197 : https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L797 */ 198 : 199 : static inline int 200 : fd_borrowed_account_checked_add_lamports( fd_borrowed_account_t * borrowed_acct, 201 0 : ulong lamports ) { 202 0 : ulong balance_post = 0UL; 203 0 : int err = fd_ulong_checked_add( borrowed_acct->acct->const_meta->info.lamports, 204 0 : lamports, 205 0 : &balance_post ); 206 0 : if( FD_UNLIKELY( err ) ) { 207 0 : return FD_EXECUTOR_INSTR_ERR_ARITHMETIC_OVERFLOW; 208 0 : } 209 : 210 0 : return fd_borrowed_account_set_lamports( borrowed_acct, balance_post ); 211 0 : } 212 : 213 : /* fd_borrowed_account_checked_sub_lamports mirrors Agave function 214 : solana_sdk::transaction_context::BorrowedAccount::checked_sub_lamports. 215 : 216 : Does not update global capitalization. Returns 0 on 217 : success or an FD_EXECUTOR_INSTR_ERR_{...} code on failure. 218 : Gracefully handles underflow. 219 : 220 : https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L807 */ 221 : 222 : static inline int 223 : fd_borrowed_account_checked_sub_lamports( fd_borrowed_account_t * borrowed_acct, 224 0 : ulong lamports ) { 225 0 : ulong balance_post = 0UL; 226 0 : int err = fd_ulong_checked_sub( borrowed_acct->acct->const_meta->info.lamports, 227 0 : lamports, 228 0 : &balance_post ); 229 0 : if( FD_UNLIKELY( err ) ) { 230 0 : return FD_EXECUTOR_INSTR_ERR_ARITHMETIC_OVERFLOW; 231 0 : } 232 : 233 0 : return fd_borrowed_account_set_lamports( borrowed_acct, balance_post ); 234 0 : } 235 : 236 : /* fd_borrowed_account_update_acounts_resize_delta mirrors Agave function 237 : solana_sdk::transaction_context:BorrowedAccount::update_accounts_resize_delta. 238 : 239 : https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L1123 */ 240 : 241 : int 242 : fd_borrowed_account_update_accounts_resize_delta( fd_borrowed_account_t * borrowed_acct, 243 : ulong new_len, 244 : int * err ); 245 : 246 : /* Accessors */ 247 : 248 : /* fd_borrowed_account_is_rent_exempt_at_data_length mirrors Agave function 249 : solana_sdk::transaction_context::BorrowedAccount::is_rent_exempt_at_data_length. 250 : 251 : Returns 1 if an account is rent exempt at it's current data length. 252 : 253 : https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L987 */ 254 : 255 : static inline int 256 0 : fd_borrowed_account_is_rent_exempt_at_data_length( fd_borrowed_account_t const * borrowed_acct ) { 257 0 : fd_txn_account_t * acct = borrowed_acct->acct; 258 0 : FD_TEST( acct->const_meta != NULL ); 259 0 : 260 0 : /* TODO: Add an is_exempt rent API to better match Agave and clean up code 261 0 : https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L990 */ 262 0 : fd_rent_t rent = borrowed_acct->instr_ctx->txn_ctx->rent; 263 0 : ulong min_balance = fd_rent_exempt_minimum_balance( &rent, acct->const_meta->dlen ); 264 0 : return acct->const_meta->info.lamports >= min_balance; 265 0 : } 266 : 267 : /* fd_borrowed_account_is_executable mirrors Agave function 268 : solana_sdk::transaction_context::BorrowedAccount::is_executable. 269 : 270 : Returns 1 if the given account has the 271 : executable flag set. Otherwise, returns 0. 272 : 273 : https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L995 */ 274 : 275 : FD_FN_PURE static inline int 276 0 : fd_borrowed_account_is_executable( fd_borrowed_account_t const * borrowed_acct ) { 277 0 : return fd_txn_account_is_executable( borrowed_acct->acct ); 278 0 : } 279 : 280 : /* fd_borrowed_account_is_executable_internal is a private function for deprecating the `is_executable` flag. 281 : It returns true if the `remove_accounts_executable_flag_checks` feature is inactive AND fd_account_is_executable 282 : return true. This is newly used in account modification logic to eventually allow "executable" accounts to be 283 : modified. 284 : 285 : https://github.com/anza-xyz/agave/blob/89872fdb074e6658646b2b57a299984f0059cc84/sdk/transaction-context/src/lib.rs#L1052-L1060 */ 286 : 287 : FD_FN_PURE static inline int 288 0 : fd_borrowed_account_is_executable_internal( fd_borrowed_account_t const * borrowed_acct ) { 289 0 : return !FD_FEATURE_ACTIVE( borrowed_acct->instr_ctx->txn_ctx->slot, borrowed_acct->instr_ctx->txn_ctx->features, remove_accounts_executable_flag_checks ) && 290 0 : fd_borrowed_account_is_executable( borrowed_acct ); 291 0 : } 292 : 293 : /* fd_borrowed_account_is_signer mirror the Agave functions 294 : solana_sdk::transaction_context::BorrowedAccount::is_signer. 295 : Returns 1 if the account is a signer or is writable and 0 otherwise. 296 : 297 : https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L1039 */ 298 : 299 : static inline int 300 0 : fd_borrowed_account_is_signer( fd_borrowed_account_t const * borrowed_acct ) { 301 0 : fd_exec_instr_ctx_t const * instr_ctx = borrowed_acct->instr_ctx; 302 0 : if( FD_UNLIKELY( borrowed_acct->instr_acc_idx >= instr_ctx->instr->acct_cnt ) ) { 303 0 : return 0; 304 0 : } 305 : 306 0 : fd_instr_info_t const * instr = instr_ctx->instr; 307 0 : return fd_instr_acc_is_signer_idx( instr, (ulong)borrowed_acct->instr_acc_idx ); 308 0 : } 309 : 310 : /* fd_borrowed_account_is_writer mirror the Agave functions 311 : solana_sdk::transaction_context::BorrowedAccount::is_writer. 312 : Returns 1 if the account is a signer or is writable and 0 otherwise. 313 : 314 : https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L1052 */ 315 : 316 : static inline int 317 0 : fd_borrowed_account_is_writable( fd_borrowed_account_t const * borrowed_acct ) { 318 0 : fd_exec_instr_ctx_t const * instr_ctx = borrowed_acct->instr_ctx; 319 0 : if( FD_UNLIKELY( borrowed_acct->instr_acc_idx >= instr_ctx->instr->acct_cnt ) ) { 320 0 : return 0; 321 0 : } 322 : 323 0 : fd_instr_info_t const * instr = instr_ctx->instr; 324 0 : return fd_instr_acc_is_writable_idx( instr, (ulong)borrowed_acct->instr_acc_idx ); 325 0 : } 326 : 327 : /* fd_borrowed_account_is_owned_by_current_program mirrors Agave's 328 : solana_sdk::transaction_context::BorrowedAccount::is_owned_by_current_program. 329 : 330 : Returns 1 if the given 331 : account is owned by the program invoked in the current instruction. 332 : Otherwise, returns 0. 333 : 334 : https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L1065 */ 335 : 336 : FD_FN_PURE static inline int 337 0 : fd_borrowed_account_is_owned_by_current_program( fd_borrowed_account_t const * borrowed_acct ) { 338 0 : return memcmp( borrowed_acct->instr_ctx->instr->program_id_pubkey.key, 339 0 : borrowed_acct->acct->const_meta->info.owner, sizeof(fd_pubkey_t) ) == 0; 340 0 : } 341 : 342 : /* fd_borrowed_account_can_data_be changed mirrors Agave function 343 : solana_sdk::transaction_context::BorrowedAccount::can_data_be_changed. 344 : 345 : https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L1074 */ 346 : 347 : static inline int 348 : fd_borrowed_account_can_data_be_changed( fd_borrowed_account_t const * borrowed_acct, 349 0 : int * err ) { 350 0 : fd_instr_info_t const * instr = borrowed_acct->instr_ctx->instr; 351 0 : FD_TEST( borrowed_acct->instr_acc_idx < instr->acct_cnt ); 352 : 353 : /* Only non-executable accounts data can be changed 354 : https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L1076 */ 355 0 : if( FD_UNLIKELY( fd_borrowed_account_is_executable_internal( borrowed_acct ) ) ) { 356 0 : *err = FD_EXECUTOR_INSTR_ERR_EXECUTABLE_DATA_MODIFIED; 357 0 : return 0; 358 0 : } 359 : 360 : /* And only if the account is writable 361 : https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L1080 */ 362 0 : if( FD_UNLIKELY( !fd_borrowed_account_is_writable( borrowed_acct ) ) ) { 363 0 : *err = FD_EXECUTOR_INSTR_ERR_READONLY_DATA_MODIFIED; 364 0 : return 0; 365 0 : } 366 : 367 : /* And only if we are the owner 368 : https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L1084 */ 369 0 : if( FD_UNLIKELY( !fd_borrowed_account_is_owned_by_current_program( borrowed_acct ) ) ) { 370 0 : *err = FD_EXECUTOR_INSTR_ERR_EXTERNAL_DATA_MODIFIED; 371 0 : return 0; 372 0 : } 373 : 374 0 : *err = FD_EXECUTOR_INSTR_SUCCESS; 375 0 : return 1; 376 0 : } 377 : 378 : /* fd_borrowed_account_can_data_be_resized mirrors Agave function 379 : solana_sdk::transaction_context::BorrowedAccount::can_data_be_resized 380 : 381 : https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L1092 */ 382 : 383 : static inline int 384 : fd_borrowed_account_can_data_be_resized( fd_borrowed_account_t const * borrowed_acct, 385 : ulong new_length, 386 0 : int * err ) { 387 0 : fd_txn_account_t * acct = borrowed_acct->acct; 388 : 389 : /* Only the owner can change the length of the data 390 : https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L1095 */ 391 0 : if( FD_UNLIKELY( ( acct->const_meta->dlen != new_length ) & 392 0 : ( !fd_borrowed_account_is_owned_by_current_program( borrowed_acct ) ) ) ) { 393 0 : *err = FD_EXECUTOR_INSTR_ERR_ACC_DATA_SIZE_CHANGED; 394 0 : return 0; 395 0 : } 396 : 397 : /* The new length can not exceed the maximum permitted length 398 : https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L1099 */ 399 0 : if( FD_UNLIKELY( new_length>MAX_PERMITTED_DATA_LENGTH ) ) { 400 0 : *err = FD_EXECUTOR_INSTR_ERR_INVALID_REALLOC; 401 0 : return 0; 402 0 : } 403 : 404 : /* The resize can not exceed the per-transaction maximum 405 : https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L1104-L1108 */ 406 0 : long length_delta = fd_long_sat_sub( (long)new_length, (long)acct->const_meta->dlen ); 407 0 : long new_accounts_resize_delta = fd_long_sat_add( (long)borrowed_acct->instr_ctx->txn_ctx->accounts_resize_delta, length_delta ); 408 0 : if( FD_UNLIKELY( new_accounts_resize_delta > MAX_PERMITTED_ACCOUNT_DATA_ALLOCS_PER_TXN ) ) { 409 0 : *err = FD_EXECUTOR_INSTR_ERR_MAX_ACCS_DATA_ALLOCS_EXCEEDED; 410 0 : return 0; 411 0 : } 412 : 413 0 : *err = FD_EXECUTOR_INSTR_SUCCESS; 414 0 : return 1; 415 0 : } 416 : 417 : FD_FN_PURE static inline int 418 0 : fd_borrowed_account_is_zeroed( fd_borrowed_account_t const * borrowed_acct ) { 419 0 : fd_txn_account_t * acct = borrowed_acct->acct; 420 : /* TODO: optimize this */ 421 0 : uchar const * data = ((uchar *) acct->const_meta) + acct->const_meta->hlen; 422 0 : for( ulong i=0UL; i < acct->const_meta->dlen; i++ ) 423 0 : if( data[i] != 0 ) 424 0 : return 0; 425 0 : return 1; 426 0 : } 427 : 428 : FD_PROTOTYPES_END 429 : 430 : #endif /* HEADER_fd_src_flamenco_runtime_fd_borrowed_account_h */