safe_associated_token_account/
processor.rs1use {
4 crate::{
5 error::AssociatedTokenAccountError,
6 instruction::AssociatedTokenAccountInstruction,
7 tools::account::{create_pda_account, get_account_len},
8 *,
9 },
10 borsh::BorshDeserialize,
11 solana_program::{
12 account_info::{next_account_info, AccountInfo},
13 entrypoint::ProgramResult,
14 msg,
15 program::{invoke, invoke_signed},
16 program_error::ProgramError,
17 pubkey::Pubkey,
18 rent::Rent,
19 system_program,
20 sysvar::Sysvar,
21 },
22 safe_token_2022::{
23 extension::{ExtensionType, StateWithExtensions},
24 state::{Account, Mint},
25 },
26};
27
28#[derive(PartialEq)]
30enum CreateMode {
31 Always,
33 Idempotent,
35}
36
37pub fn process_instruction(
39 program_id: &Pubkey,
40 accounts: &[AccountInfo],
41 input: &[u8],
42) -> ProgramResult {
43 let instruction = if input.is_empty() {
44 AssociatedTokenAccountInstruction::Create
45 } else {
46 AssociatedTokenAccountInstruction::try_from_slice(input)
47 .map_err(|_| ProgramError::InvalidInstructionData)?
48 };
49
50 msg!("{:?}", instruction);
51
52 match instruction {
53 AssociatedTokenAccountInstruction::Create => {
54 process_create_associated_token_account(program_id, accounts, CreateMode::Always)
55 }
56 AssociatedTokenAccountInstruction::CreateIdempotent => {
57 process_create_associated_token_account(program_id, accounts, CreateMode::Idempotent)
58 }
59 AssociatedTokenAccountInstruction::RecoverNested => {
60 process_recover_nested(program_id, accounts)
61 }
62 }
63}
64
65fn process_create_associated_token_account(
67 program_id: &Pubkey,
68 accounts: &[AccountInfo],
69 create_mode: CreateMode,
70) -> ProgramResult {
71 let account_info_iter = &mut accounts.iter();
72
73 let funder_info = next_account_info(account_info_iter)?;
74 let associated_token_account_info = next_account_info(account_info_iter)?;
75 let wallet_account_info = next_account_info(account_info_iter)?;
76 let safe_token_mint_info = next_account_info(account_info_iter)?;
77 let system_program_info = next_account_info(account_info_iter)?;
78 let safe_token_program_info = next_account_info(account_info_iter)?;
79 let safe_token_program_id = safe_token_program_info.key;
80
81 let (associated_token_address, bump_seed) = get_associated_token_address_and_bump_seed_internal(
82 wallet_account_info.key,
83 safe_token_mint_info.key,
84 program_id,
85 safe_token_program_id,
86 );
87 if associated_token_address != *associated_token_account_info.key {
88 msg!("Error: Associated address does not match seed derivation");
89 return Err(ProgramError::InvalidSeeds);
90 }
91
92 if create_mode == CreateMode::Idempotent
93 && associated_token_account_info.owner == safe_token_program_id
94 {
95 let ata_data = associated_token_account_info.data.borrow();
96 if let Ok(associated_token_account) = StateWithExtensions::<Account>::unpack(&ata_data) {
97 if associated_token_account.base.owner != *wallet_account_info.key {
98 let error = AssociatedTokenAccountError::InvalidOwner;
99 msg!("{}", error);
100 return Err(error.into());
101 }
102 if associated_token_account.base.mint != *safe_token_mint_info.key {
103 return Err(ProgramError::InvalidAccountData);
104 }
105 return Ok(());
106 }
107 }
108 if *associated_token_account_info.owner != system_program::id() {
109 return Err(ProgramError::IllegalOwner);
110 }
111
112 let rent = Rent::get()?;
113
114 let associated_token_account_signer_seeds: &[&[_]] = &[
115 &wallet_account_info.key.to_bytes(),
116 &safe_token_program_id.to_bytes(),
117 &safe_token_mint_info.key.to_bytes(),
118 &[bump_seed],
119 ];
120
121 let account_len = get_account_len(
122 safe_token_mint_info,
123 safe_token_program_info,
124 &[ExtensionType::ImmutableOwner],
125 )?;
126
127 create_pda_account(
128 funder_info,
129 &rent,
130 account_len,
131 safe_token_program_id,
132 system_program_info,
133 associated_token_account_info,
134 associated_token_account_signer_seeds,
135 )?;
136
137 msg!("Initialize the associated token account");
138 invoke(
139 &safe_token_2022::instruction::initialize_immutable_owner(
140 safe_token_program_id,
141 associated_token_account_info.key,
142 )?,
143 &[
144 associated_token_account_info.clone(),
145 safe_token_program_info.clone(),
146 ],
147 )?;
148 invoke(
149 &safe_token_2022::instruction::initialize_account3(
150 safe_token_program_id,
151 associated_token_account_info.key,
152 safe_token_mint_info.key,
153 wallet_account_info.key,
154 )?,
155 &[
156 associated_token_account_info.clone(),
157 safe_token_mint_info.clone(),
158 wallet_account_info.clone(),
159 safe_token_program_info.clone(),
160 ],
161 )
162}
163
164pub fn process_recover_nested(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
166 let account_info_iter = &mut accounts.iter();
167
168 let nested_associated_token_account_info = next_account_info(account_info_iter)?;
169 let nested_token_mint_info = next_account_info(account_info_iter)?;
170 let destination_associated_token_account_info = next_account_info(account_info_iter)?;
171 let owner_associated_token_account_info = next_account_info(account_info_iter)?;
172 let owner_token_mint_info = next_account_info(account_info_iter)?;
173 let wallet_account_info = next_account_info(account_info_iter)?;
174 let safe_token_program_info = next_account_info(account_info_iter)?;
175 let safe_token_program_id = safe_token_program_info.key;
176
177 let (owner_associated_token_address, bump_seed) =
179 get_associated_token_address_and_bump_seed_internal(
180 wallet_account_info.key,
181 owner_token_mint_info.key,
182 program_id,
183 safe_token_program_id,
184 );
185 if owner_associated_token_address != *owner_associated_token_account_info.key {
186 msg!("Error: Owner associated address does not match seed derivation");
187 return Err(ProgramError::InvalidSeeds);
188 }
189
190 let (nested_associated_token_address, _) = get_associated_token_address_and_bump_seed_internal(
192 owner_associated_token_account_info.key,
193 nested_token_mint_info.key,
194 program_id,
195 safe_token_program_id,
196 );
197 if nested_associated_token_address != *nested_associated_token_account_info.key {
198 msg!("Error: Nested associated address does not match seed derivation");
199 return Err(ProgramError::InvalidSeeds);
200 }
201
202 let (destination_associated_token_address, _) =
204 get_associated_token_address_and_bump_seed_internal(
205 wallet_account_info.key,
206 nested_token_mint_info.key,
207 program_id,
208 safe_token_program_id,
209 );
210 if destination_associated_token_address != *destination_associated_token_account_info.key {
211 msg!("Error: Destination associated address does not match seed derivation");
212 return Err(ProgramError::InvalidSeeds);
213 }
214
215 if !wallet_account_info.is_signer {
216 msg!("Wallet of the owner associated token account must sign");
217 return Err(ProgramError::MissingRequiredSignature);
218 }
219
220 if owner_token_mint_info.owner != safe_token_program_id {
221 msg!("Owner mint not owned by provided token program");
222 return Err(ProgramError::IllegalOwner);
223 }
224
225 let (amount, decimals) = {
228 if owner_associated_token_account_info.owner != safe_token_program_id {
230 msg!("Owner associated token account not owned by provided token program, recreate the owner associated token account first");
231 return Err(ProgramError::IllegalOwner);
232 }
233 let owner_account_data = owner_associated_token_account_info.data.borrow();
234 let owner_account = StateWithExtensions::<Account>::unpack(&owner_account_data)?;
235 if owner_account.base.owner != *wallet_account_info.key {
236 msg!("Owner associated token account not owned by provided wallet");
237 return Err(AssociatedTokenAccountError::InvalidOwner.into());
238 }
239
240 if nested_associated_token_account_info.owner != safe_token_program_id {
242 msg!("Nested associated token account not owned by provided token program");
243 return Err(ProgramError::IllegalOwner);
244 }
245 let nested_account_data = nested_associated_token_account_info.data.borrow();
246 let nested_account = StateWithExtensions::<Account>::unpack(&nested_account_data)?;
247 if nested_account.base.owner != *owner_associated_token_account_info.key {
248 msg!("Nested associated token account not owned by provided associated token account");
249 return Err(AssociatedTokenAccountError::InvalidOwner.into());
250 }
251 let amount = nested_account.base.amount;
252
253 if nested_token_mint_info.owner != safe_token_program_id {
255 msg!("Nested mint account not owned by provided token program");
256 return Err(ProgramError::IllegalOwner);
257 }
258 let nested_mint_data = nested_token_mint_info.data.borrow();
259 let nested_mint = StateWithExtensions::<Mint>::unpack(&nested_mint_data)?;
260 let decimals = nested_mint.base.decimals;
261 (amount, decimals)
262 };
263
264 let owner_associated_token_account_signer_seeds: &[&[_]] = &[
266 &wallet_account_info.key.to_bytes(),
267 &safe_token_program_id.to_bytes(),
268 &owner_token_mint_info.key.to_bytes(),
269 &[bump_seed],
270 ];
271 invoke_signed(
272 &safe_token_2022::instruction::transfer_checked(
273 safe_token_program_id,
274 nested_associated_token_account_info.key,
275 nested_token_mint_info.key,
276 destination_associated_token_account_info.key,
277 owner_associated_token_account_info.key,
278 &[],
279 amount,
280 decimals,
281 )?,
282 &[
283 nested_associated_token_account_info.clone(),
284 nested_token_mint_info.clone(),
285 destination_associated_token_account_info.clone(),
286 owner_associated_token_account_info.clone(),
287 safe_token_program_info.clone(),
288 ],
289 &[owner_associated_token_account_signer_seeds],
290 )?;
291
292 invoke_signed(
294 &safe_token_2022::instruction::close_account(
295 safe_token_program_id,
296 nested_associated_token_account_info.key,
297 wallet_account_info.key,
298 owner_associated_token_account_info.key,
299 &[],
300 )?,
301 &[
302 nested_associated_token_account_info.clone(),
303 wallet_account_info.clone(),
304 owner_associated_token_account_info.clone(),
305 safe_token_program_info.clone(),
306 ],
307 &[owner_associated_token_account_signer_seeds],
308 )
309}