11use anchor_lang:: prelude:: * ;
2- use anchor_spl:: associated_token:: get_associated_token_address_with_program_id;
32use solana_program:: address_lookup_table:: state:: AddressLookupTable ;
43
5- use crate :: { router_accounts:: TokenAdminRegistry , seed, CommonCcipError } ;
4+ use crate :: {
5+ context:: TokenAccountsValidationContext , router_accounts:: TokenAdminRegistry , CommonCcipError ,
6+ } ;
67
78pub struct TokenAccounts < ' a > {
89 pub user_token_account : & ' a AccountInfo < ' a > ,
@@ -25,125 +26,58 @@ pub fn validate_and_parse_token_accounts<'info>(
2526 fee_quoter : Pubkey ,
2627 accounts : & ' info [ AccountInfo < ' info > ] ,
2728) -> Result < TokenAccounts > {
29+ // The program_id here is provided solely to satisfy the interface of try_accounts.
30+ // Note: All program IDs for PDA derivation are explicitly defined in the account context
31+ // (TokenAccountsValidationContext) via seeds and program attributes.
32+ // Therefore, the value of program_id (set here to Pubkey::default()) is effectively unused.
33+ // Changes in environment-specific program addresses will not affect the PDA derivation.
34+ let program_id = Pubkey :: default ( ) ;
35+
36+ let mut input_accounts = accounts;
37+ let mut bumps = <TokenAccountsValidationContext as anchor_lang:: Bumps >:: Bumps :: default ( ) ;
38+ let mut reallocs = std:: collections:: BTreeSet :: new ( ) ;
39+
40+ // leveraging Anchor's account context validation
41+ // Instead of manually checking each account (ownership, PDA derivation, constraints),
42+ // we're using Anchor's `try_accounts` to perform these validations based on the
43+ // constraints defined in the `TokenAccountsValidationContext` account context struct
44+ TokenAccountsValidationContext :: try_accounts (
45+ & program_id,
46+ & mut input_accounts,
47+ & [
48+ token_receiver. as_ref ( ) ,
49+ & chain_selector. to_le_bytes ( ) ,
50+ router. as_ref ( ) ,
51+ fee_quoter. as_ref ( ) ,
52+ ]
53+ . concat ( ) ,
54+ & mut bumps,
55+ & mut reallocs,
56+ ) ?;
57+
58+ let mut accounts_iter = accounts. iter ( ) ;
59+
2860 // accounts based on user or chain
29- let ( user_token_account, remaining_accounts ) = accounts . split_first ( ) . unwrap ( ) ;
30- let ( token_billing_config, remaining_accounts ) = remaining_accounts . split_first ( ) . unwrap ( ) ;
31- let ( pool_chain_config, remaining_accounts ) = remaining_accounts . split_first ( ) . unwrap ( ) ;
61+ let user_token_account = next_account_info ( & mut accounts_iter ) ? ;
62+ let token_billing_config = next_account_info ( & mut accounts_iter ) ? ;
63+ let pool_chain_config = next_account_info ( & mut accounts_iter ) ? ;
3264
3365 // constant accounts for any pool interaction
34- let ( lookup_table, remaining_accounts) = remaining_accounts. split_first ( ) . unwrap ( ) ;
35- let ( token_admin_registry, remaining_accounts) = remaining_accounts. split_first ( ) . unwrap ( ) ;
36- let ( pool_program, remaining_accounts) = remaining_accounts. split_first ( ) . unwrap ( ) ;
37- let ( pool_config, remaining_accounts) = remaining_accounts. split_first ( ) . unwrap ( ) ;
38- let ( pool_token_account, remaining_accounts) = remaining_accounts. split_first ( ) . unwrap ( ) ;
39- let ( pool_signer, remaining_accounts) = remaining_accounts. split_first ( ) . unwrap ( ) ;
40- let ( token_program, remaining_accounts) = remaining_accounts. split_first ( ) . unwrap ( ) ;
41- let ( mint, remaining_accounts) = remaining_accounts. split_first ( ) . unwrap ( ) ;
42- let ( fee_token_config, remaining_accounts) = remaining_accounts. split_first ( ) . unwrap ( ) ;
43-
44- // Account validations (using remaining_accounts does not facilitate built-in anchor checks)
66+ let lookup_table = next_account_info ( & mut accounts_iter) ?;
67+ let token_admin_registry = next_account_info ( & mut accounts_iter) ?;
68+ let pool_program = next_account_info ( & mut accounts_iter) ?;
69+ let pool_config = next_account_info ( & mut accounts_iter) ?;
70+ let pool_token_account = next_account_info ( & mut accounts_iter) ?;
71+ let pool_signer = next_account_info ( & mut accounts_iter) ?;
72+ let token_program = next_account_info ( & mut accounts_iter) ?;
73+ let mint = next_account_info ( & mut accounts_iter) ?;
74+ let fee_token_config = next_account_info ( & mut accounts_iter) ?;
75+
76+ // collect remaining accounts
77+ let remaining_accounts = accounts_iter. as_slice ( ) ;
78+
79+ // Additional validations that can't be expressed in the account context
4580 {
46- // Check Token Admin Registry
47- let ( expected_token_admin_registry, _) = Pubkey :: find_program_address (
48- & [ seed:: TOKEN_ADMIN_REGISTRY , mint. key ( ) . as_ref ( ) ] ,
49- & router,
50- ) ;
51- require_eq ! (
52- token_admin_registry. key( ) ,
53- expected_token_admin_registry,
54- CommonCcipError :: InvalidInputsTokenAdminRegistryAccounts
55- ) ;
56-
57- // check pool program + pool config + pool signer
58- let ( expected_pool_config, _) = Pubkey :: find_program_address (
59- & [ seed:: CCIP_TOKENPOOL_CONFIG , mint. key ( ) . as_ref ( ) ] ,
60- & pool_program. key ( ) ,
61- ) ;
62- let ( expected_pool_signer, _) = Pubkey :: find_program_address (
63- & [ seed:: CCIP_TOKENPOOL_SIGNER , mint. key ( ) . as_ref ( ) ] ,
64- & pool_program. key ( ) ,
65- ) ;
66- require_eq ! (
67- * pool_config. owner,
68- pool_program. key( ) ,
69- CommonCcipError :: InvalidInputsPoolAccounts
70- ) ;
71- require_eq ! (
72- pool_config. key( ) ,
73- expected_pool_config,
74- CommonCcipError :: InvalidInputsPoolAccounts
75- ) ;
76- require_eq ! (
77- pool_signer. key( ) ,
78- expected_pool_signer,
79- CommonCcipError :: InvalidInputsPoolAccounts
80- ) ;
81-
82- let ( expected_fee_token_config, _) = Pubkey :: find_program_address (
83- & [ seed:: FEE_BILLING_TOKEN_CONFIG , mint. key . as_ref ( ) ] ,
84- & fee_quoter,
85- ) ;
86- require_eq ! (
87- fee_token_config. key( ) ,
88- expected_fee_token_config,
89- CommonCcipError :: InvalidInputsConfigAccounts
90- ) ;
91-
92- // check token accounts
93- require_eq ! (
94- * mint. owner,
95- token_program. key( ) ,
96- CommonCcipError :: InvalidInputsTokenAccounts
97- ) ;
98- require_eq ! (
99- user_token_account. key( ) ,
100- get_associated_token_address_with_program_id(
101- & token_receiver,
102- & mint. key( ) ,
103- & token_program. key( )
104- ) ,
105- CommonCcipError :: InvalidInputsTokenAccounts
106- ) ;
107- require_eq ! (
108- pool_token_account. key( ) ,
109- get_associated_token_address_with_program_id(
110- & pool_signer. key( ) ,
111- & mint. key( ) ,
112- & token_program. key( )
113- ) ,
114- CommonCcipError :: InvalidInputsTokenAccounts
115- ) ;
116-
117- // check per token per chain configs
118- // billing: configured via CCIP fee quoter
119- // chain config: configured via pool
120- let ( expected_billing_config, _) = Pubkey :: find_program_address (
121- & [
122- seed:: PER_CHAIN_PER_TOKEN_CONFIG ,
123- chain_selector. to_le_bytes ( ) . as_ref ( ) ,
124- mint. key ( ) . as_ref ( ) ,
125- ] ,
126- & fee_quoter,
127- ) ;
128- let ( expected_pool_chain_config, _) = Pubkey :: find_program_address (
129- & [
130- seed:: TOKEN_POOL_CONFIG ,
131- chain_selector. to_le_bytes ( ) . as_ref ( ) ,
132- mint. key ( ) . as_ref ( ) ,
133- ] ,
134- & pool_program. key ( ) ,
135- ) ;
136- require_eq ! (
137- token_billing_config. key( ) ,
138- expected_billing_config, // TODO: determine if this can be zero key for optional billing config?
139- CommonCcipError :: InvalidInputsConfigAccounts
140- ) ;
141- require_eq ! (
142- pool_chain_config. key( ) ,
143- expected_pool_chain_config,
144- CommonCcipError :: InvalidInputsConfigAccounts
145- ) ;
146-
14781 // Check Lookup Table Address configured in TokenAdminRegistry
14882 let token_admin_registry_account: Account < TokenAdminRegistry > =
14983 Account :: try_from ( token_admin_registry) ?;
0 commit comments