diff --git a/Cargo.lock b/Cargo.lock index cae11fcb..b92b8677 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aead" @@ -212,7 +212,7 @@ dependencies = [ "solana-program", "spl-associated-token-account", "spl-token", - "spl-token-2022", + "spl-token-2022 0.9.0", ] [[package]] @@ -1355,11 +1355,12 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.0" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70bf6736f74634d299d00086f02986875b3c2d924781a6a2cb6c201e73da0ceb" +checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" dependencies = [ - "num_enum_derive 0.7.0", + "num_enum_derive 0.7.4", + "rustversion", ] [[package]] @@ -1376,9 +1377,9 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.0" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ea360eafe1022f7cc56cd7b869ed57330fb2453d0c7831d99b74c65d2f5597" +checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", @@ -2069,9 +2070,9 @@ dependencies = [ [[package]] name = "spl-associated-token-account" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385e31c29981488f2820b2022d8e731aae3b02e6e18e2fd854e4c9a94dc44fc3" +checksum = "992d9c64c2564cc8f63a4b508bf3ebcdf2254b0429b13cd1d31adb6162432a5f" dependencies = [ "assert_matches", "borsh 0.10.4", @@ -2079,7 +2080,7 @@ dependencies = [ "num-traits", "solana-program", "spl-token", - "spl-token-2022", + "spl-token-2022 1.0.0", "thiserror", ] @@ -2179,6 +2180,20 @@ dependencies = [ "spl-type-length-value", ] +[[package]] +name = "spl-tlv-account-resolution" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "615d381f48ddd2bb3c57c7f7fb207591a2a05054639b18a62e785117dd7a8683" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator", + "spl-pod", + "spl-program-error", + "spl-type-length-value", +] + [[package]] name = "spl-token" version = "4.0.0" @@ -2204,18 +2219,55 @@ dependencies = [ "bytemuck", "num-derive 0.4.2", "num-traits", - "num_enum 0.7.0", + "num_enum 0.7.4", "solana-program", "solana-zk-token-sdk", "spl-memo", "spl-pod", "spl-token", "spl-token-metadata-interface", - "spl-transfer-hook-interface", + "spl-transfer-hook-interface 0.3.0", "spl-type-length-value", "thiserror", ] +[[package]] +name = "spl-token-2022" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d697fac19fd74ff472dfcc13f0b442dd71403178ce1de7b5d16f83a33561c059" +dependencies = [ + "arrayref", + "bytemuck", + "num-derive 0.4.2", + "num-traits", + "num_enum 0.7.4", + "solana-program", + "solana-security-txt", + "solana-zk-token-sdk", + "spl-memo", + "spl-pod", + "spl-token", + "spl-token-group-interface", + "spl-token-metadata-interface", + "spl-transfer-hook-interface 0.4.1", + "spl-type-length-value", + "thiserror", +] + +[[package]] +name = "spl-token-group-interface" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b889509d49fa74a4a033ca5dae6c2307e9e918122d97e58562f5c4ffa795c75d" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator", + "spl-pod", + "spl-program-error", +] + [[package]] name = "spl-token-metadata-interface" version = "0.2.0" @@ -2242,7 +2294,23 @@ dependencies = [ "spl-discriminator", "spl-pod", "spl-program-error", - "spl-tlv-account-resolution", + "spl-tlv-account-resolution 0.4.0", + "spl-type-length-value", +] + +[[package]] +name = "spl-transfer-hook-interface" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aabdb7c471566f6ddcee724beb8618449ea24b399e58d464d6b5bc7db550259" +dependencies = [ + "arrayref", + "bytemuck", + "solana-program", + "spl-discriminator", + "spl-pod", + "spl-program-error", + "spl-tlv-account-resolution 0.5.1", "spl-type-length-value", ] diff --git a/programs/launchpad/src/instructions/mod.rs b/programs/launchpad/src/instructions/mod.rs index 3cc8c52b..23481e70 100644 --- a/programs/launchpad/src/instructions/mod.rs +++ b/programs/launchpad/src/instructions/mod.rs @@ -5,6 +5,7 @@ pub mod fund; pub mod initialize_launch; pub mod refund; pub mod start_launch; +pub mod recover_nested_tokens; pub use claim::*; pub use close_launch::*; @@ -13,3 +14,4 @@ pub use fund::*; pub use initialize_launch::*; pub use refund::*; pub use start_launch::*; +pub use recover_nested_tokens::*; diff --git a/programs/launchpad/src/instructions/recover_nested_tokens.rs b/programs/launchpad/src/instructions/recover_nested_tokens.rs new file mode 100644 index 00000000..e866aab3 --- /dev/null +++ b/programs/launchpad/src/instructions/recover_nested_tokens.rs @@ -0,0 +1,64 @@ +use anchor_lang::prelude::*; +use anchor_spl::{associated_token::AssociatedToken, token::{Mint, Token, TokenAccount}}; + +use crate::{state::{Launch, associated_token_program::{RecoverNested, recover_nested}}}; + +#[event_cpi] +#[derive(Accounts)] +pub struct RecoverNestedTokens<'info> { + #[account( + mut, + has_one = launch_quote_vault, + has_one = launch_signer, + )] + pub launch: Account<'info, Launch>, + + #[account(mut)] + pub launch_quote_vault: Account<'info, TokenAccount>, + #[account( + mut, + associated_token::authority = launch_quote_vault, + associated_token::mint = launch_quote_vault.mint + )] + pub nested_launch_quote_vault: Account<'info, TokenAccount>, + #[account( + address = launch_quote_vault.mint + )] + pub mint: Account<'info, Mint>, + + /// CHECK: just a signer + pub launch_signer: UncheckedAccount<'info>, + + pub token_program: Program<'info, Token>, + pub associated_token_program: Program<'info, AssociatedToken>, +} + +impl RecoverNestedTokens<'_> { + pub fn handle(ctx: Context) -> Result<()> { + let seeds = &[ + b"launch_signer", + ctx.accounts.launch.to_account_info().key.as_ref(), + &[ctx.accounts.launch.launch_signer_pda_bump], + ]; + let signer = &[&seeds[..]]; + + let accounts = RecoverNested { + nested_associated_account_address: ctx.accounts.nested_launch_quote_vault.to_account_info(), + nested_associated_mint_address: ctx.accounts.mint.to_account_info(), + destination_associated_account_address: ctx.accounts.launch_quote_vault.to_account_info(), + owner_associated_account_address: ctx.accounts.launch_quote_vault.to_account_info(), + owner_token_mint_address: ctx.accounts.mint.to_account_info(), + wallet_address: ctx.accounts.launch_signer.to_account_info(), + token_program: ctx.accounts.token_program.to_account_info(), + associated_token_program: ctx.accounts.associated_token_program.to_account_info(), + }; + + recover_nested( + CpiContext::new_with_signer( + ctx.accounts.associated_token_program.to_account_info(), + accounts, + signer + ) + ) + } +} diff --git a/programs/launchpad/src/lib.rs b/programs/launchpad/src/lib.rs index 70a11abd..e6b32a32 100644 --- a/programs/launchpad/src/lib.rs +++ b/programs/launchpad/src/lib.rs @@ -90,4 +90,8 @@ pub mod launchpad { pub fn close_launch(ctx: Context) -> Result<()> { CloseLaunch::handle(ctx) } + + pub fn recover_nested_tokens(ctx: Context) -> Result<()> { + RecoverNestedTokens::handle(ctx) + } } diff --git a/programs/launchpad/src/state/associated_token_program.rs b/programs/launchpad/src/state/associated_token_program.rs new file mode 100644 index 00000000..1f59acfc --- /dev/null +++ b/programs/launchpad/src/state/associated_token_program.rs @@ -0,0 +1,56 @@ +use anchor_lang::prelude::*; + +use anchor_spl::associated_token::AssociatedToken; +use solana_program::instruction::Instruction; + +use super::*; + +pub fn recover_nested<'info>( + ctx: CpiContext<'_, '_, '_, 'info, RecoverNested<'info>>, +) -> Result<()> { + + // RecoverNested instruction + let instruction_data = vec![2]; + + let ix = Instruction { + program_id: ctx.accounts.associated_token_program.key(), + accounts: vec![ + AccountMeta::new(ctx.accounts.nested_associated_account_address.key(), false), + AccountMeta::new_readonly(ctx.accounts.nested_associated_mint_address.key(), false), + AccountMeta::new(ctx.accounts.destination_associated_account_address.key(), false), + AccountMeta::new_readonly(ctx.accounts.owner_associated_account_address.key(), false), + AccountMeta::new_readonly(ctx.accounts.owner_token_mint_address.key(), false), + AccountMeta::new(ctx.accounts.wallet_address.key(), true), + AccountMeta::new_readonly(ctx.accounts.token_program.key(), false), + ], + data: instruction_data.try_to_vec().unwrap() + }; + + solana_program::program::invoke_signed( + &ix, + &[ + ctx.accounts.nested_associated_account_address, + ctx.accounts.nested_associated_mint_address, + ctx.accounts.destination_associated_account_address, + ctx.accounts.owner_associated_account_address, + ctx.accounts.owner_token_mint_address, + ctx.accounts.wallet_address, + ctx.accounts.token_program + ], + ctx.signer_seeds, + ) + .map_err(Into::into) +} + +#[derive(Accounts)] +pub struct RecoverNested<'info> { + pub nested_associated_account_address: AccountInfo<'info>, + pub nested_associated_mint_address: AccountInfo<'info>, + pub destination_associated_account_address: AccountInfo<'info>, + pub owner_associated_account_address: AccountInfo<'info>, + pub owner_token_mint_address: AccountInfo<'info>, + pub wallet_address: AccountInfo<'info>, + pub token_program: AccountInfo<'info>, + #[account(address = AssociatedToken::id())] + pub associated_token_program: AccountInfo<'info>, +} \ No newline at end of file diff --git a/programs/launchpad/src/state/mod.rs b/programs/launchpad/src/state/mod.rs index d1d1addd..f586d846 100644 --- a/programs/launchpad/src/state/mod.rs +++ b/programs/launchpad/src/state/mod.rs @@ -1,5 +1,7 @@ +pub mod associated_token_program; pub mod funding_record; pub mod launch; +pub use associated_token_program::*; pub use funding_record::*; pub use launch::*;