safe_associated_token_account/
processor.rs

1//! Program state processor
2
3use {
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/// Specify when to create the associated token account
29#[derive(PartialEq)]
30enum CreateMode {
31    /// Always try to create the ATA
32    Always,
33    /// Only try to create the ATA if non-existent
34    Idempotent,
35}
36
37/// Instruction processor
38pub 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
65/// Processes CreateAssociatedTokenAccount instruction
66fn 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
164/// Processes `RecoverNested` instruction
165pub 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    // Check owner address derivation
178    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    // Check nested address derivation
191    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    // Check destination address derivation
203    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    // Account data is dropped at the end of this, so the CPI can succeed
226    // without a double-borrow
227    let (amount, decimals) = {
228        // Check owner associated token account data
229        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        // Check nested associated token account data
241        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        // Check nested token mint data
254        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    // Transfer everything out
265    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    // Close the nested account so it's never used again
293    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}