|
12 | 12 | account_info::{next_account_info, AccountInfo},
|
13 | 13 | entrypoint::ProgramResult,
|
14 | 14 | msg,
|
15 |
| - program::invoke, |
| 15 | + program::{invoke, invoke_signed}, |
16 | 16 | program_error::ProgramError,
|
17 | 17 | pubkey::Pubkey,
|
18 | 18 | rent::Rent,
|
|
21 | 21 | },
|
22 | 22 | spl_token_2022::{
|
23 | 23 | extension::{ExtensionType, StateWithExtensions},
|
24 |
| - state::Account, |
| 24 | + state::{Account, Mint}, |
25 | 25 | },
|
26 | 26 | };
|
27 | 27 |
|
@@ -56,6 +56,9 @@ pub fn process_instruction(
|
56 | 56 | AssociatedTokenAccountInstruction::CreateIdempotent => {
|
57 | 57 | process_create_associated_token_account(program_id, accounts, CreateMode::Idempotent)
|
58 | 58 | }
|
| 59 | + AssociatedTokenAccountInstruction::RecoverNested => { |
| 60 | + process_recover_nested(program_id, accounts) |
| 61 | + } |
59 | 62 | }
|
60 | 63 | }
|
61 | 64 |
|
@@ -157,3 +160,150 @@ fn process_create_associated_token_account(
|
157 | 160 | ],
|
158 | 161 | )
|
159 | 162 | }
|
| 163 | + |
| 164 | +/// Processes `RecoverNested` instruction |
| 165 | +pub 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 spl_token_program_info = next_account_info(account_info_iter)?; |
| 175 | + let spl_token_program_id = spl_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 | + spl_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 | + spl_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 | + spl_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 != spl_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 != spl_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 != spl_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 != spl_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 | + &spl_token_program_id.to_bytes(), |
| 268 | + &owner_token_mint_info.key.to_bytes(), |
| 269 | + &[bump_seed], |
| 270 | + ]; |
| 271 | + invoke_signed( |
| 272 | + &spl_token_2022::instruction::transfer_checked( |
| 273 | + spl_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 | + spl_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 | + &spl_token_2022::instruction::close_account( |
| 295 | + spl_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 | + spl_token_program_info.clone(), |
| 306 | + ], |
| 307 | + &[owner_associated_token_account_signer_seeds], |
| 308 | + ) |
| 309 | +} |
0 commit comments