@@ -80,6 +80,7 @@ pub(crate) struct ValidatedTransactionDetails {
8080 pub ( crate ) compute_budget_limits : ComputeBudgetLimits ,
8181 pub ( crate ) fee_details : FeeDetails ,
8282 pub ( crate ) loaded_fee_payer_account : LoadedTransactionAccount ,
83+ pub ( crate ) fee_payer_address : Pubkey ,
8384}
8485
8586#[ derive( PartialEq , Eq , Debug , Clone ) ]
@@ -208,21 +209,48 @@ impl<'a, CB: TransactionProcessingCallback> AccountLoader<'a, CB> {
208209 & executed_transaction. loaded_transaction . accounts ,
209210 ) ;
210211 } else {
212+ let fee_payer_address = self . effective_fee_payer_address_for_failed_tx ( message) ;
211213 self . update_accounts_for_failed_tx (
212- message,
213214 & executed_transaction. loaded_transaction . rollback_accounts ,
215+ & fee_payer_address,
214216 ) ;
215217 }
216218 }
217219
218- pub ( crate ) fn update_accounts_for_failed_tx (
220+ /// If the fee payer is delegated, use it. Otherwise, load the escrow
221+ /// account if delegated
222+ pub ( crate ) fn effective_fee_payer_address_for_failed_tx (
219223 & mut self ,
220224 message : & impl SVMMessage ,
225+ ) -> Pubkey {
226+ use crate :: escrow:: ephemeral_balance_pda_from_payer;
227+ let fee_payer_address = * message. fee_payer ( ) ;
228+ let mut is_delegated = |addr : & Pubkey | -> bool {
229+ self . load_account ( addr, true )
230+ . map ( |acc| acc. account . delegated ( ) )
231+ . unwrap_or ( false )
232+ } ;
233+ // If the fee payer is delegated, use it
234+ if is_delegated ( & fee_payer_address) {
235+ return fee_payer_address;
236+ }
237+ // Otherwise, load the escrow account if delegated
238+ let escrow_address = ephemeral_balance_pda_from_payer ( & fee_payer_address) ;
239+ if is_delegated ( & escrow_address) {
240+ return escrow_address;
241+ }
242+ fee_payer_address
243+ }
244+
245+ pub ( crate ) fn update_accounts_for_failed_tx (
246+ & mut self ,
221247 rollback_accounts : & RollbackAccounts ,
248+ fee_payer_address : & Pubkey ,
222249 ) {
223- let fee_payer_address = message. fee_payer ( ) ;
224250 match rollback_accounts {
225- RollbackAccounts :: FeePayerOnly { fee_payer_account } => {
251+ RollbackAccounts :: FeePayerOnly {
252+ fee_payer_account, ..
253+ } => {
226254 self . account_cache
227255 . insert ( * fee_payer_address, fee_payer_account. clone ( ) ) ;
228256 }
@@ -233,6 +261,7 @@ impl<'a, CB: TransactionProcessingCallback> AccountLoader<'a, CB> {
233261 RollbackAccounts :: SeparateNonceAndFeePayer {
234262 nonce,
235263 fee_payer_account,
264+ ..
236265 } => {
237266 self . account_cache
238267 . insert ( * nonce. address ( ) , nonce. account ( ) . clone ( ) ) ;
@@ -360,6 +389,7 @@ pub(crate) fn load_transaction<CB: TransactionProcessingCallback>(
360389 let load_result = load_transaction_accounts (
361390 account_loader,
362391 message,
392+ & tx_details. fee_payer_address ,
363393 tx_details. loaded_fee_payer_account ,
364394 & tx_details. compute_budget_limits ,
365395 error_metrics,
@@ -399,6 +429,7 @@ struct LoadedTransactionAccounts {
399429fn load_transaction_accounts < CB : TransactionProcessingCallback > (
400430 account_loader : & mut AccountLoader < CB > ,
401431 message : & impl SVMMessage ,
432+ fee_payer_address : & Pubkey ,
402433 loaded_fee_payer_account : LoadedTransactionAccount ,
403434 compute_budget_limits : & ComputeBudgetLimits ,
404435 error_metrics : & mut TransactionErrorMetrics ,
@@ -434,7 +465,7 @@ fn load_transaction_accounts<CB: TransactionProcessingCallback>(
434465
435466 // Since the fee payer is always the first account, collect it first.
436467 // We can use it directly because it was already loaded during validation.
437- collect_loaded_account ( message . fee_payer ( ) , loaded_fee_payer_account) ?;
468+ collect_loaded_account ( fee_payer_address , loaded_fee_payer_account) ?;
438469
439470 // Attempt to load and collect remaining non-fee payer accounts
440471 for ( account_index, account_key) in account_keys. iter ( ) . enumerate ( ) . skip ( 1 ) {
@@ -1043,10 +1074,26 @@ mod tests {
10431074 Arc :: new ( FeatureSet :: all_enabled ( ) ) ,
10441075 0 ,
10451076 ) ;
1077+ // Build proper ValidatedTransactionDetails with real fee payer
1078+ let fee_payer = * tx. message ( ) . fee_payer ( ) ;
1079+ // In some tests we don't pass actual accounts; default to a zeroed account for fee payer
1080+ let fee_payer_account = callbacks
1081+ . accounts_map
1082+ . get ( & fee_payer)
1083+ . cloned ( )
1084+ . unwrap_or_else ( || AccountSharedData :: default ( ) ) ;
1085+ let validation_details = ValidatedTransactionDetails {
1086+ fee_payer_address : fee_payer,
1087+ loaded_fee_payer_account : LoadedTransactionAccount {
1088+ account : fee_payer_account,
1089+ ..LoadedTransactionAccount :: default ( )
1090+ } ,
1091+ ..ValidatedTransactionDetails :: default ( )
1092+ } ;
10461093 load_transaction (
10471094 & mut account_loader,
10481095 & tx,
1049- Ok ( ValidatedTransactionDetails :: default ( ) ) ,
1096+ Ok ( validation_details ) ,
10501097 & mut error_metrics,
10511098 & RentCollector :: default ( ) ,
10521099 )
@@ -1205,16 +1252,19 @@ mod tests {
12051252 }
12061253 }
12071254
1208- // If payer account has no balance, expected AccountNotFound Error
1255+ // If payer account has no balance, expected InsufficientFundsForFee Error
12091256 // regardless feature gate status, or if payer is nonce account.
1257+ // NOTE: solana svm returns AccountNotFound, but since we support not existing (signer)
1258+ // accounts as fee payer (when validator fees = 0), we return InsufficientFundsForFee
1259+ // instead.
12101260 {
12111261 for is_nonce in [ true , false ] {
12121262 validate_fee_payer_account (
12131263 ValidateFeePayerTestParameter {
12141264 is_nonce,
12151265 payer_init_balance : 0 ,
12161266 fee,
1217- expected_result : Err ( TransactionError :: AccountNotFound ) ,
1267+ expected_result : Err ( TransactionError :: InsufficientFundsForFee ) ,
12181268 payer_post_balance : 0 ,
12191269 } ,
12201270 & rent_collector,
@@ -1331,6 +1381,7 @@ mod tests {
13311381 let result = load_transaction_accounts (
13321382 & mut account_loader,
13331383 sanitized_transaction. message ( ) ,
1384+ & fee_payer_address,
13341385 LoadedTransactionAccount {
13351386 loaded_size : fee_payer_account. data ( ) . len ( ) ,
13361387 account : fee_payer_account. clone ( ) ,
@@ -1394,6 +1445,7 @@ mod tests {
13941445 let result = load_transaction_accounts (
13951446 & mut account_loader,
13961447 sanitized_transaction. message ( ) ,
1448+ & key1. pubkey ( ) ,
13971449 LoadedTransactionAccount {
13981450 account : fee_payer_account. clone ( ) ,
13991451 ..LoadedTransactionAccount :: default ( )
@@ -1454,6 +1506,7 @@ mod tests {
14541506 let result = load_transaction_accounts (
14551507 & mut account_loader,
14561508 sanitized_transaction. message ( ) ,
1509+ & key1. pubkey ( ) ,
14571510 LoadedTransactionAccount :: default ( ) ,
14581511 & ComputeBudgetLimits :: default ( ) ,
14591512 & mut error_metrics,
@@ -1496,6 +1549,7 @@ mod tests {
14961549 let result = load_transaction_accounts (
14971550 & mut account_loader,
14981551 sanitized_transaction. message ( ) ,
1552+ & key1. pubkey ( ) ,
14991553 LoadedTransactionAccount :: default ( ) ,
15001554 & ComputeBudgetLimits :: default ( ) ,
15011555 & mut error_metrics,
@@ -1549,6 +1603,7 @@ mod tests {
15491603 let result = load_transaction_accounts (
15501604 & mut account_loader,
15511605 sanitized_transaction. message ( ) ,
1606+ & key2. pubkey ( ) ,
15521607 LoadedTransactionAccount {
15531608 account : fee_payer_account. clone ( ) ,
15541609 ..LoadedTransactionAccount :: default ( )
@@ -1613,6 +1668,7 @@ mod tests {
16131668 let result = load_transaction_accounts (
16141669 & mut account_loader,
16151670 sanitized_transaction. message ( ) ,
1671+ & key2. pubkey ( ) ,
16161672 LoadedTransactionAccount :: default ( ) ,
16171673 & ComputeBudgetLimits :: default ( ) ,
16181674 & mut error_metrics,
@@ -1665,6 +1721,7 @@ mod tests {
16651721 let result = load_transaction_accounts (
16661722 & mut account_loader,
16671723 sanitized_transaction. message ( ) ,
1724+ & key2. pubkey ( ) ,
16681725 LoadedTransactionAccount :: default ( ) ,
16691726 & ComputeBudgetLimits :: default ( ) ,
16701727 & mut error_metrics,
@@ -1725,6 +1782,7 @@ mod tests {
17251782 let result = load_transaction_accounts (
17261783 & mut account_loader,
17271784 sanitized_transaction. message ( ) ,
1785+ & key2. pubkey ( ) ,
17281786 LoadedTransactionAccount {
17291787 account : fee_payer_account. clone ( ) ,
17301788 ..LoadedTransactionAccount :: default ( )
@@ -1808,6 +1866,7 @@ mod tests {
18081866 let result = load_transaction_accounts (
18091867 & mut account_loader,
18101868 sanitized_transaction. message ( ) ,
1869+ & key2. pubkey ( ) ,
18111870 LoadedTransactionAccount {
18121871 account : fee_payer_account. clone ( ) ,
18131872 ..LoadedTransactionAccount :: default ( )
@@ -1960,6 +2019,7 @@ mod tests {
19602019 account : fee_payer_account,
19612020 ..LoadedTransactionAccount :: default ( )
19622021 } ,
2022+ fee_payer_address : key2. pubkey ( ) ,
19632023 ..ValidatedTransactionDetails :: default ( )
19642024 } ) ;
19652025
@@ -2321,6 +2381,7 @@ mod tests {
23212381 let loaded_transaction_accounts = load_transaction_accounts (
23222382 & mut account_loader,
23232383 & transaction,
2384+ & fee_payer,
23242385 LoadedTransactionAccount {
23252386 account : fee_payer_account. clone ( ) ,
23262387 loaded_size : fee_payer_size as usize ,
0 commit comments