diff --git a/Cargo.lock b/Cargo.lock index 0bbbe13ac3..d22ed1782b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3820,6 +3820,7 @@ dependencies = [ "solana-sdk", "solana-transaction-status", "spl-token", + "spl-token-2022 3.0.4", "thiserror", "tokio", ] @@ -6175,8 +6176,8 @@ dependencies = [ "solana-sdk", "spl-token", "spl-token-2022 1.0.0", - "spl-token-group-interface", - "spl-token-metadata-interface", + "spl-token-group-interface 0.1.0", + "spl-token-metadata-interface 0.2.0", "thiserror", "zstd", ] @@ -7676,7 +7677,18 @@ checksum = "cce5d563b58ef1bb2cdbbfe0dfb9ffdc24903b10ae6a4df2d8f425ece375033f" dependencies = [ "bytemuck", "solana-program", - "spl-discriminator-derive", + "spl-discriminator-derive 0.1.2", +] + +[[package]] +name = "spl-discriminator" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "210101376962bb22bb13be6daea34656ea1cbc248fce2164b146e39203b55e03" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator-derive 0.2.0", ] [[package]] @@ -7686,7 +7698,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07fd7858fc4ff8fb0e34090e41d7eb06a823e1057945c26d480bfc21d2338a93" dependencies = [ "quote", - "spl-discriminator-syn", + "spl-discriminator-syn 0.1.2", + "syn 2.0.85", +] + +[[package]] +name = "spl-discriminator-derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9e8418ea6269dcfb01c712f0444d2c75542c04448b480e87de59d2865edc750" +dependencies = [ + "quote", + "spl-discriminator-syn 0.2.0", "syn 2.0.85", ] @@ -7703,6 +7726,19 @@ dependencies = [ "thiserror", ] +[[package]] +name = "spl-discriminator-syn" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1f05593b7ca9eac7caca309720f2eafb96355e037e6d373b909a80fe7b69b9" +dependencies = [ + "proc-macro2", + "quote", + "sha2 0.10.8", + "syn 2.0.85", + "thiserror", +] + [[package]] name = "spl-memo" version = "4.0.0" @@ -7731,7 +7767,20 @@ dependencies = [ "bytemuck", "solana-program", "solana-zk-token-sdk", - "spl-program-error", + "spl-program-error 0.3.0", +] + +[[package]] +name = "spl-pod" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c52d84c55efeef8edcc226743dc089d7e3888b8e3474569aa3eff152b37b9996" +dependencies = [ + "borsh 1.5.1", + "bytemuck", + "solana-program", + "solana-zk-token-sdk", + "spl-program-error 0.4.4", ] [[package]] @@ -7743,7 +7792,20 @@ dependencies = [ "num-derive 0.4.2", "num-traits", "solana-program", - "spl-program-error-derive", + "spl-program-error-derive 0.3.2", + "thiserror", +] + +[[package]] +name = "spl-program-error" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e45a49acb925db68aa501b926096b2164adbdcade7a0c24152af9f0742d0a602" +dependencies = [ + "num-derive 0.4.2", + "num-traits", + "solana-program", + "spl-program-error-derive 0.4.1", "thiserror", ] @@ -7759,6 +7821,18 @@ dependencies = [ "syn 2.0.85", ] +[[package]] +name = "spl-program-error-derive" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d375dd76c517836353e093c2dbb490938ff72821ab568b545fd30ab3256b3e" +dependencies = [ + "proc-macro2", + "quote", + "sha2 0.10.8", + "syn 2.0.85", +] + [[package]] name = "spl-tlv-account-resolution" version = "0.4.0" @@ -7767,10 +7841,10 @@ checksum = "062e148d3eab7b165582757453632ffeef490c02c86a48bfdb4988f63eefb3b9" dependencies = [ "bytemuck", "solana-program", - "spl-discriminator", - "spl-pod", - "spl-program-error", - "spl-type-length-value", + "spl-discriminator 0.1.0", + "spl-pod 0.1.0", + "spl-program-error 0.3.0", + "spl-type-length-value 0.3.0", ] [[package]] @@ -7781,10 +7855,24 @@ checksum = "615d381f48ddd2bb3c57c7f7fb207591a2a05054639b18a62e785117dd7a8683" dependencies = [ "bytemuck", "solana-program", - "spl-discriminator", - "spl-pod", - "spl-program-error", - "spl-type-length-value", + "spl-discriminator 0.1.0", + "spl-pod 0.1.0", + "spl-program-error 0.3.0", + "spl-type-length-value 0.3.0", +] + +[[package]] +name = "spl-tlv-account-resolution" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fab8edfd37be5fa17c9e42c1bff86abbbaf0494b031b37957f2728ad2ff842ba" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator 0.2.5", + "spl-pod 0.2.5", + "spl-program-error 0.4.4", + "spl-type-length-value 0.4.6", ] [[package]] @@ -7816,11 +7904,11 @@ dependencies = [ "solana-program", "solana-zk-token-sdk", "spl-memo", - "spl-pod", + "spl-pod 0.1.0", "spl-token", - "spl-token-metadata-interface", + "spl-token-metadata-interface 0.2.0", "spl-transfer-hook-interface 0.3.0", - "spl-type-length-value", + "spl-type-length-value 0.3.0", "thiserror", ] @@ -7839,12 +7927,36 @@ dependencies = [ "solana-security-txt", "solana-zk-token-sdk", "spl-memo", - "spl-pod", + "spl-pod 0.1.0", "spl-token", - "spl-token-group-interface", - "spl-token-metadata-interface", + "spl-token-group-interface 0.1.0", + "spl-token-metadata-interface 0.2.0", "spl-transfer-hook-interface 0.4.1", - "spl-type-length-value", + "spl-type-length-value 0.3.0", + "thiserror", +] + +[[package]] +name = "spl-token-2022" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b01d1b2851964e257187c0bca43a0de38d0af59192479ca01ac3e2b58b1bd95a" +dependencies = [ + "arrayref", + "bytemuck", + "num-derive 0.4.2", + "num-traits", + "num_enum 0.7.3", + "solana-program", + "solana-security-txt", + "solana-zk-token-sdk", + "spl-memo", + "spl-pod 0.2.5", + "spl-token", + "spl-token-group-interface 0.2.5", + "spl-token-metadata-interface 0.3.5", + "spl-transfer-hook-interface 0.6.5", + "spl-type-length-value 0.4.6", "thiserror", ] @@ -7856,9 +7968,22 @@ checksum = "b889509d49fa74a4a033ca5dae6c2307e9e918122d97e58562f5c4ffa795c75d" dependencies = [ "bytemuck", "solana-program", - "spl-discriminator", - "spl-pod", - "spl-program-error", + "spl-discriminator 0.1.0", + "spl-pod 0.1.0", + "spl-program-error 0.3.0", +] + +[[package]] +name = "spl-token-group-interface" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "014817d6324b1e20c4bbc883e8ee30a5faa13e59d91d1b2b95df98b920150c17" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator 0.2.5", + "spl-pod 0.2.5", + "spl-program-error 0.4.4", ] [[package]] @@ -7869,10 +7994,24 @@ checksum = "4c16ce3ba6979645fb7627aa1e435576172dd63088dc7848cb09aa331fa1fe4f" dependencies = [ "borsh 0.10.3", "solana-program", - "spl-discriminator", - "spl-pod", - "spl-program-error", - "spl-type-length-value", + "spl-discriminator 0.1.0", + "spl-pod 0.1.0", + "spl-program-error 0.3.0", + "spl-type-length-value 0.3.0", +] + +[[package]] +name = "spl-token-metadata-interface" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3da00495b602ebcf5d8ba8b3ecff1ee454ce4c125c9077747be49c2d62335ba" +dependencies = [ + "borsh 1.5.1", + "solana-program", + "spl-discriminator 0.2.5", + "spl-pod 0.2.5", + "spl-program-error 0.4.4", + "spl-type-length-value 0.4.6", ] [[package]] @@ -7884,11 +8023,11 @@ dependencies = [ "arrayref", "bytemuck", "solana-program", - "spl-discriminator", - "spl-pod", - "spl-program-error", + "spl-discriminator 0.1.0", + "spl-pod 0.1.0", + "spl-program-error 0.3.0", "spl-tlv-account-resolution 0.4.0", - "spl-type-length-value", + "spl-type-length-value 0.3.0", ] [[package]] @@ -7900,11 +8039,27 @@ dependencies = [ "arrayref", "bytemuck", "solana-program", - "spl-discriminator", - "spl-pod", - "spl-program-error", + "spl-discriminator 0.1.0", + "spl-pod 0.1.0", + "spl-program-error 0.3.0", "spl-tlv-account-resolution 0.5.1", - "spl-type-length-value", + "spl-type-length-value 0.3.0", +] + +[[package]] +name = "spl-transfer-hook-interface" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9b5c08a89838e5a2931f79b17f611857f281a14a2100968a3ccef352cb7414b" +dependencies = [ + "arrayref", + "bytemuck", + "solana-program", + "spl-discriminator 0.2.5", + "spl-pod 0.2.5", + "spl-program-error 0.4.4", + "spl-tlv-account-resolution 0.6.5", + "spl-type-length-value 0.4.6", ] [[package]] @@ -7915,9 +8070,22 @@ checksum = "a468e6f6371f9c69aae760186ea9f1a01c2908351b06a5e0026d21cfc4d7ecac" dependencies = [ "bytemuck", "solana-program", - "spl-discriminator", - "spl-pod", - "spl-program-error", + "spl-discriminator 0.1.0", + "spl-pod 0.1.0", + "spl-program-error 0.3.0", +] + +[[package]] +name = "spl-type-length-value" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c872f93d0600e743116501eba2d53460e73a12c9a496875a42a7d70e034fe06d" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator 0.2.5", + "spl-pod 0.2.5", + "spl-program-error 0.4.4", ] [[package]] diff --git a/programs/compressed-token/src/instructions/mint_to.rs b/programs/compressed-token/src/instructions/mint_to.rs new file mode 100644 index 0000000000..a91d9aa9b4 --- /dev/null +++ b/programs/compressed-token/src/instructions/mint_to.rs @@ -0,0 +1,240 @@ +use account_compression::{program::AccountCompression, utils::constants::CPI_AUTHORITY_PDA_SEED}; +use anchor_lang::prelude::*; +use anchor_spl::{ + token::{Mint, Token, TokenAccount}, + token_2022::Token2022, + token_interface::{Mint as Mint22, TokenAccount as Token22Account}, +}; +use light_system_program::program::LightSystemProgram; + +use crate::{program::LightCompressedToken, POOL_SEED}; + +#[derive(Accounts)] +pub struct MintToInstruction<'info> { + /// UNCHECKED: only pays fees. + #[account(mut)] + pub fee_payer: Signer<'info>, + /// CHECK: is checked by mint account macro. + pub authority: Signer<'info>, + /// CHECK: + #[account(seeds = [CPI_AUTHORITY_PDA_SEED], bump)] + pub cpi_authority_pda: UncheckedAccount<'info>, + /// CHECK: that authority is mint authority + #[account( + mut, + constraint = mint.mint_authority.unwrap() == authority.key() + @ crate::ErrorCode::InvalidAuthorityMint + )] + pub mint: Account<'info, Mint>, + /// CHECK: this account is checked implictly since a mint to from a mint + /// account to a token account of a different mint will fail + #[account(mut, seeds = [POOL_SEED, &mint.key().to_bytes()],bump)] + pub token_pool_pda: Account<'info, TokenAccount>, + pub token_program: Program<'info, Token>, + pub light_system_program: Program<'info, LightSystemProgram>, + /// CHECK: (different program) checked in account compression program + pub registered_program_pda: UncheckedAccount<'info>, + /// CHECK: (different program) checked in system and account compression + /// programs + pub noop_program: UncheckedAccount<'info>, + /// CHECK: this account in account compression program + #[account(seeds = [CPI_AUTHORITY_PDA_SEED], bump, seeds::program = light_system_program::ID)] + pub account_compression_authority: UncheckedAccount<'info>, + /// CHECK: this account in account compression program + pub account_compression_program: Program<'info, AccountCompression>, + /// CHECK: (different program) will be checked by the system program + #[account(mut)] + pub merkle_tree: UncheckedAccount<'info>, + /// CHECK: (different program) will be checked by the system program + pub self_program: Program<'info, LightCompressedToken>, + pub system_program: Program<'info, System>, + /// CHECK: (different program) will be checked by the system program + #[account(mut)] + pub sol_pool_pda: Option>, +} + +#[derive(Accounts)] +pub struct MintToInstruction22<'info> { + /// UNCHECKED: only pays fees. + #[account(mut)] + pub fee_payer: Signer<'info>, + /// CHECK: is checked by mint account macro. + pub authority: Signer<'info>, + /// CHECK: + #[account(seeds = [CPI_AUTHORITY_PDA_SEED], bump)] + pub cpi_authority_pda: UncheckedAccount<'info>, + /// CHECK: that authority is mint authority + #[account( + mut, + constraint = mint.mint_authority.unwrap() == authority.key() + @ crate::ErrorCode::InvalidAuthorityMint + )] + pub mint: InterfaceAccount<'info, Mint22>, + /// CHECK: this account is checked implictly since a mint to from a mint + /// account to a token account of a different mint will fail + #[account(mut, seeds = [POOL_SEED, &mint.key().to_bytes()],bump)] + pub token_pool_pda: InterfaceAccount<'info, Token22Account>, + pub token_program: Program<'info, Token2022>, + pub light_system_program: Program<'info, LightSystemProgram>, + /// CHECK: (different program) checked in account compression program + pub registered_program_pda: UncheckedAccount<'info>, + /// CHECK: (different program) checked in system and account compression + /// programs + pub noop_program: UncheckedAccount<'info>, + /// CHECK: this account in account compression program + #[account(seeds = [CPI_AUTHORITY_PDA_SEED], bump, seeds::program = light_system_program::ID)] + pub account_compression_authority: UncheckedAccount<'info>, + /// CHECK: this account in account compression program + pub account_compression_program: Program<'info, AccountCompression>, + /// CHECK: (different program) will be checked by the system program + #[account(mut)] + pub merkle_tree: UncheckedAccount<'info>, + /// CHECK: (different program) will be checked by the system program + pub self_program: Program<'info, LightCompressedToken>, + pub system_program: Program<'info, System>, + /// CHECK: (different program) will be checked by the system program + #[account(mut)] + pub sol_pool_pda: Option>, +} + +pub trait MintToAccounts<'info> { + fn get_fee_payer(&self) -> AccountInfo<'info>; + fn get_authority(&self) -> AccountInfo<'info>; + fn get_cpi_authority_pda(&self) -> AccountInfo<'info>; + fn get_mint(&self) -> AccountInfo<'info>; + fn get_token_pool_pda(&self) -> AccountInfo<'info>; + fn get_token_program(&self) -> AccountInfo<'info>; + fn get_light_system_program(&self) -> AccountInfo<'info>; + fn get_registered_program_pda(&self) -> AccountInfo<'info>; + fn get_noop_program(&self) -> AccountInfo<'info>; + fn get_account_compression_authority(&self) -> AccountInfo<'info>; + fn get_account_compression_program(&self) -> AccountInfo<'info>; + fn get_merkle_tree(&self) -> AccountInfo<'info>; + fn get_self_program(&self) -> AccountInfo<'info>; + fn get_system_program(&self) -> AccountInfo<'info>; + fn get_sol_pool_pda(&self) -> Option>; +} + +impl<'info> MintToAccounts<'info> for MintToInstruction<'info> { + fn get_fee_payer(&self) -> AccountInfo<'info> { + self.fee_payer.to_account_info() + } + + fn get_authority(&self) -> AccountInfo<'info> { + self.authority.to_account_info() + } + + fn get_cpi_authority_pda(&self) -> AccountInfo<'info> { + self.cpi_authority_pda.to_account_info() + } + + fn get_mint(&self) -> AccountInfo<'info> { + self.mint.to_account_info() + } + + fn get_token_pool_pda(&self) -> AccountInfo<'info> { + self.token_pool_pda.to_account_info() + } + + fn get_token_program(&self) -> AccountInfo<'info> { + self.token_program.to_account_info() + } + + fn get_light_system_program(&self) -> AccountInfo<'info> { + self.light_system_program.to_account_info() + } + + fn get_registered_program_pda(&self) -> AccountInfo<'info> { + self.registered_program_pda.to_account_info() + } + + fn get_noop_program(&self) -> AccountInfo<'info> { + self.noop_program.to_account_info() + } + + fn get_account_compression_authority(&self) -> AccountInfo<'info> { + self.account_compression_authority.to_account_info() + } + + fn get_account_compression_program(&self) -> AccountInfo<'info> { + self.account_compression_program.to_account_info() + } + + fn get_merkle_tree(&self) -> AccountInfo<'info> { + self.merkle_tree.to_account_info() + } + + fn get_self_program(&self) -> AccountInfo<'info> { + self.self_program.to_account_info() + } + + fn get_system_program(&self) -> AccountInfo<'info> { + self.system_program.to_account_info() + } + + fn get_sol_pool_pda(&self) -> Option> { + self.sol_pool_pda.as_ref().map(|account| account.clone()) + } +} + +impl<'info> MintToAccounts<'info> for MintToInstruction22<'info> { + fn get_fee_payer(&self) -> AccountInfo<'info> { + self.fee_payer.to_account_info() + } + + fn get_authority(&self) -> AccountInfo<'info> { + self.authority.to_account_info() + } + + fn get_cpi_authority_pda(&self) -> AccountInfo<'info> { + self.cpi_authority_pda.to_account_info() + } + + fn get_mint(&self) -> AccountInfo<'info> { + self.mint.to_account_info() + } + + fn get_token_pool_pda(&self) -> AccountInfo<'info> { + self.token_pool_pda.to_account_info() + } + + fn get_token_program(&self) -> AccountInfo<'info> { + self.token_program.to_account_info() + } + + fn get_light_system_program(&self) -> AccountInfo<'info> { + self.light_system_program.to_account_info() + } + + fn get_registered_program_pda(&self) -> AccountInfo<'info> { + self.registered_program_pda.to_account_info() + } + + fn get_noop_program(&self) -> AccountInfo<'info> { + self.noop_program.to_account_info() + } + + fn get_account_compression_authority(&self) -> AccountInfo<'info> { + self.account_compression_authority.to_account_info() + } + + fn get_account_compression_program(&self) -> AccountInfo<'info> { + self.account_compression_program.to_account_info() + } + + fn get_merkle_tree(&self) -> AccountInfo<'info> { + self.merkle_tree.to_account_info() + } + + fn get_self_program(&self) -> AccountInfo<'info> { + self.self_program.to_account_info() + } + + fn get_system_program(&self) -> AccountInfo<'info> { + self.system_program.to_account_info() + } + + fn get_sol_pool_pda(&self) -> Option> { + self.sol_pool_pda.as_ref().map(|account| account.clone()) + } +} diff --git a/programs/compressed-token/src/instructions/mod.rs b/programs/compressed-token/src/instructions/mod.rs index f6a4ee7727..a5c0e1e78b 100644 --- a/programs/compressed-token/src/instructions/mod.rs +++ b/programs/compressed-token/src/instructions/mod.rs @@ -1,8 +1,10 @@ pub mod burn; pub mod freeze; pub mod generic; +pub mod mint_to; pub mod transfer; pub use burn::*; pub use freeze::*; pub use generic::*; +pub use mint_to::*; pub use transfer::*; diff --git a/programs/compressed-token/src/instructions/transfer.rs b/programs/compressed-token/src/instructions/transfer.rs index 73ffa0ef3e..078cd0a4e7 100644 --- a/programs/compressed-token/src/instructions/transfer.rs +++ b/programs/compressed-token/src/instructions/transfer.rs @@ -1,6 +1,10 @@ use account_compression::{program::AccountCompression, utils::constants::CPI_AUTHORITY_PDA_SEED}; use anchor_lang::prelude::*; -use anchor_spl::token::{Token, TokenAccount}; +use anchor_spl::{ + token::{Token, TokenAccount}, + token_2022::Token2022, + token_interface::TokenAccount as Token22Account, +}; use light_system_program::{ self, program::LightSystemProgram, @@ -41,6 +45,109 @@ pub struct TransferInstruction<'info> { pub system_program: Program<'info, System>, } +#[derive(Accounts)] +pub struct TransferInstruction22<'info> { + /// UNCHECKED: only pays fees. + #[account(mut)] + pub fee_payer: Signer<'info>, + /// CHECK: + /// Authority is verified through proof since both owner and delegate + /// are included in the token data hash, which is a public input to the + /// validity proof. + pub authority: Signer<'info>, + /// CHECK: (seed constraint). + #[account(seeds = [CPI_AUTHORITY_PDA_SEED], bump,)] + pub cpi_authority_pda: UncheckedAccount<'info>, + pub light_system_program: Program<'info, LightSystemProgram>, + /// CHECK: (account compression program). + pub registered_program_pda: AccountInfo<'info>, + /// CHECK: (account compression program) when emitting event. + pub noop_program: UncheckedAccount<'info>, + /// CHECK: (different program) is used to cpi account compression program from light system program. + pub account_compression_authority: UncheckedAccount<'info>, + pub account_compression_program: Program<'info, AccountCompression>, + /// CHECK:(system program) used to derive cpi_authority_pda and check that + /// this program is the signer of the cpi. + pub self_program: Program<'info, LightCompressedToken>, + #[account(mut)] + pub token_pool_pda: Option>, + #[account(mut, constraint= if token_pool_pda.is_some() {Ok(token_pool_pda.as_ref().unwrap().key() != compress_or_decompress_token_account.key())}else {err!(crate::ErrorCode::TokenPoolPdaUndefined)}? @crate::ErrorCode::IsTokenPoolPda)] + pub compress_or_decompress_token_account: Option>, + pub token_program: Option>, + pub system_program: Program<'info, System>, +} + +pub trait TokenAccounts<'info> { + fn get_token_pool_pda(&self) -> Option>; + fn get_compress_or_decompress_token_account(&self) -> Option>; + fn get_token_program(&self) -> Option>; + fn get_self_program(&self) -> AccountInfo<'info>; + fn get_cpi_authority(&self) -> AccountInfo<'info>; + fn get_light_system_program(&self) -> AccountInfo<'info>; +} + +impl<'info> TokenAccounts<'info> for TransferInstruction<'info> { + fn get_token_pool_pda(&self) -> Option> { + match &self.token_pool_pda { + Some(account) => Some(account.to_account_info()), + None => None, + } + } + + fn get_compress_or_decompress_token_account(&self) -> Option> { + match &self.compress_or_decompress_token_account { + Some(account) => Some(account.to_account_info()), + None => None, + } + } + + fn get_token_program(&self) -> Option> { + match &self.token_program { + Some(account) => Some(account.to_account_info()), + None => None, + } + } + fn get_self_program(&self) -> AccountInfo<'info> { + self.self_program.to_account_info() + } + fn get_cpi_authority(&self) -> AccountInfo<'info> { + self.cpi_authority_pda.to_account_info() + } + fn get_light_system_program(&self) -> AccountInfo<'info> { + self.light_system_program.to_account_info() + } +} +impl<'info> TokenAccounts<'info> for TransferInstruction22<'info> { + fn get_token_pool_pda(&self) -> Option> { + match &self.token_pool_pda { + Some(account) => Some(account.to_account_info()), + None => None, + } + } + + fn get_compress_or_decompress_token_account(&self) -> Option> { + match &self.compress_or_decompress_token_account { + Some(account) => Some(account.to_account_info()), + None => None, + } + } + + fn get_token_program(&self) -> Option> { + match &self.token_program { + Some(account) => Some(account.to_account_info()), + None => None, + } + } + fn get_self_program(&self) -> AccountInfo<'info> { + self.self_program.to_account_info() + } + fn get_cpi_authority(&self) -> AccountInfo<'info> { + self.cpi_authority_pda.to_account_info() + } + fn get_light_system_program(&self) -> AccountInfo<'info> { + self.light_system_program.to_account_info() + } +} // TODO: transform all to account info impl<'info> InvokeAccounts<'info> for TransferInstruction<'info> { fn get_registered_program_pda(&self) -> &AccountInfo<'info> { @@ -81,3 +188,44 @@ impl<'info> SignerAccounts<'info> for TransferInstruction<'info> { &self.authority } } + +// TODO: transform all to account info +impl<'info> InvokeAccounts<'info> for TransferInstruction22<'info> { + fn get_registered_program_pda(&self) -> &AccountInfo<'info> { + &self.registered_program_pda + } + + fn get_noop_program(&self) -> &UncheckedAccount<'info> { + &self.noop_program + } + + fn get_account_compression_authority(&self) -> &UncheckedAccount<'info> { + &self.account_compression_authority + } + + fn get_account_compression_program(&self) -> &Program<'info, AccountCompression> { + &self.account_compression_program + } + + fn get_system_program(&self) -> &Program<'info, System> { + &self.system_program + } + + fn get_sol_pool_pda(&self) -> Option<&AccountInfo<'info>> { + None + } + + fn get_decompression_recipient(&self) -> Option<&AccountInfo<'info>> { + None + } +} + +impl<'info> SignerAccounts<'info> for TransferInstruction22<'info> { + fn get_fee_payer(&self) -> &Signer<'info> { + &self.fee_payer + } + + fn get_authority(&self) -> &Signer<'info> { + &self.authority + } +} diff --git a/programs/compressed-token/src/lib.rs b/programs/compressed-token/src/lib.rs index 17fe527ef7..05edd26439 100644 --- a/programs/compressed-token/src/lib.rs +++ b/programs/compressed-token/src/lib.rs @@ -41,6 +41,12 @@ pub mod light_compressed_token { Ok(()) } + pub fn create_token_pool22<'info>( + _ctx: Context<'_, '_, '_, 'info, CreateTokenPoolInstruction22<'info>>, + ) -> Result<()> { + Ok(()) + } + /// Mints tokens from an spl token mint to a list of compressed accounts. /// Minted tokens are transferred to a pool account owned by the compressed /// token program. The instruction creates one compressed output account for @@ -57,6 +63,15 @@ pub mod light_compressed_token { process_mint_to(ctx, public_keys, amounts, lamports) } + pub fn mint_to22<'info>( + ctx: Context<'_, '_, '_, 'info, MintToInstruction22<'info>>, + public_keys: Vec, + amounts: Vec, + lamports: Option, + ) -> Result<()> { + process_mint_to(ctx, public_keys, amounts, lamports) + } + /// Transfers compressed tokens from one account to another. All accounts /// must be of the same mint. Additional spl tokens can be compressed or /// decompressed. In one transaction only compression or decompression is @@ -72,6 +87,21 @@ pub mod light_compressed_token { process_transfer::process_transfer(ctx, inputs) } + /// Transfers compressed tokens from one account to another. All accounts + /// must be of the same mint. Additional spl tokens can be compressed or + /// decompressed. In one transaction only compression or decompression is + /// possible. Lamports can be transferred alongside tokens. If output token + /// accounts specify less lamports than inputs the remaining lamports are + /// transferred to an output compressed account. Signer must be owner or + /// delegate. If a delegated token account is transferred the delegate is + /// not preserved. + pub fn transfer_22<'info>( + ctx: Context<'_, '_, '_, 'info, TransferInstruction22<'info>>, + inputs: Vec, + ) -> Result<()> { + process_transfer::process_transfer(ctx, inputs) + } + /// Delegates an amount to a delegate. A compressed token account is either /// completely delegated or not. Prior delegates are not preserved. Cannot /// be called by a delegate. diff --git a/programs/compressed-token/src/process_mint.rs b/programs/compressed-token/src/process_mint.rs index 332f481f05..fbef9bdc38 100644 --- a/programs/compressed-token/src/process_mint.rs +++ b/programs/compressed-token/src/process_mint.rs @@ -1,9 +1,13 @@ -use account_compression::{program::AccountCompression, utils::constants::CPI_AUTHORITY_PDA_SEED}; -use anchor_lang::prelude::*; -use anchor_spl::token::{Mint, Token, TokenAccount}; +use account_compression::utils::constants::CPI_AUTHORITY_PDA_SEED; +use anchor_lang::{prelude::*, Bumps}; +use anchor_spl::{ + token::{Mint, Token, TokenAccount}, + token_2022::{self, Token2022}, + token_interface::{Mint as Mint22, TokenAccount as Token22Account}, +}; -use crate::program::LightCompressedToken; -use light_system_program::{program::LightSystemProgram, OutputCompressedAccountWithPackedContext}; +use crate::MintToAccounts; +use light_system_program::OutputCompressedAccountWithPackedContext; #[cfg(target_os = "solana")] use { @@ -42,6 +46,33 @@ pub struct CreateTokenPoolInstruction<'info> { pub cpi_authority_pda: AccountInfo<'info>, } +#[derive(Accounts)] +pub struct CreateTokenPoolInstruction22<'info> { + /// UNCHECKED: only pays fees. + #[account(mut)] + pub fee_payer: Signer<'info>, + /// CHECK: init manually + #[account( + init, + seeds = [ + POOL_SEED, &mint.key().to_bytes(), + ], + bump, + payer = fee_payer, + token::mint = mint, + token::authority = cpi_authority_pda, + )] + pub token_pool_pda: InterfaceAccount<'info, Token22Account>, + pub system_program: Program<'info, System>, + /// CHECK: is mint account. + #[account(mut)] + pub mint: InterfaceAccount<'info, Mint22>, + pub token_program: Program<'info, Token2022>, + /// CHECK: (seeds anchor constraint). + #[account(seeds = [CPI_AUTHORITY_PDA_SEED], bump)] + pub cpi_authority_pda: AccountInfo<'info>, +} + /// Mints tokens from an spl token mint to a list of compressed accounts and /// stores minted tokens in spl token pool account. /// @@ -55,8 +86,8 @@ pub struct CreateTokenPoolInstruction<'info> { /// pre_compressed_acounts_pos. /// 5. Invoke system program to execute the compressed transaction. #[allow(unused_variables)] -pub fn process_mint_to<'info>( - ctx: Context<'_, '_, '_, 'info, MintToInstruction<'info>>, +pub fn process_mint_to<'info, A: MintToAccounts<'info> + Bumps>( + ctx: Context<'_, '_, '_, 'info, A>, recipient_pubkeys: Vec, amounts: Vec, lamports: Option, @@ -97,17 +128,16 @@ pub fn process_mint_to<'info>( mint_spl_to_pool_pda(&ctx, &amounts)?; bench_sbf_end!("tm_mint_spl_to_pool_pda"); - let hashed_mint = - hash_to_bn254_field_size_be(ctx.accounts.mint.to_account_info().key().as_ref()) - .unwrap() - .0; + let hashed_mint = hash_to_bn254_field_size_be(ctx.accounts.get_mint().key().as_ref()) + .unwrap() + .0; bench_sbf_start!("tm_output_compressed_accounts"); let mut output_compressed_accounts = vec![OutputCompressedAccountWithPackedContext::default(); recipient_pubkeys.len()]; let lamports_vec = lamports.map(|_| vec![lamports; amounts.len()]); create_output_compressed_accounts( &mut output_compressed_accounts, - ctx.accounts.mint.to_account_info().key(), + ctx.accounts.get_mint().key(), recipient_pubkeys.as_slice(), None, None, @@ -143,8 +173,8 @@ pub fn process_mint_to<'info>( #[cfg(target_os = "solana")] #[inline(never)] -pub fn cpi_execute_compressed_transaction_mint_to<'info>( - ctx: &Context<'_, '_, '_, 'info, MintToInstruction<'info>>, +pub fn cpi_execute_compressed_transaction_mint_to<'info, A: MintToAccounts<'info> + Bumps>( + ctx: &Context<'_, '_, '_, 'info, A>, output_compressed_accounts: Vec, inputs: &mut Vec, pre_compressed_acounts_pos: usize, @@ -166,28 +196,29 @@ pub fn cpi_execute_compressed_transaction_mint_to<'info>( let instructiondata = light_system_program::instruction::InvokeCpi { inputs: inputs.to_owned(), }; - let (sol_pool_pda, is_writable) = if let Some(pool_pda) = ctx.accounts.sol_pool_pda.as_ref() { - // Account is some - (pool_pda.to_account_info(), true) - } else { - // Account is None - (ctx.accounts.light_system_program.to_account_info(), false) - }; + let (sol_pool_pda, is_writable) = + if let Some(pool_pda) = ctx.accounts.get_sol_pool_pda().as_ref() { + // Account is some + (pool_pda.to_account_info(), true) + } else { + // Account is None + (ctx.accounts.get_light_system_program(), false) + }; // 1300 CU let account_infos = vec![ - ctx.accounts.fee_payer.to_account_info(), - ctx.accounts.cpi_authority_pda.to_account_info(), - ctx.accounts.registered_program_pda.to_account_info(), - ctx.accounts.noop_program.to_account_info(), - ctx.accounts.account_compression_authority.to_account_info(), - ctx.accounts.account_compression_program.to_account_info(), - ctx.accounts.self_program.to_account_info(), + ctx.accounts.get_fee_payer(), + ctx.accounts.get_cpi_authority_pda(), + ctx.accounts.get_registered_program_pda(), + ctx.accounts.get_noop_program(), + ctx.accounts.get_account_compression_authority(), + ctx.accounts.get_account_compression_program(), + ctx.accounts.get_self_program(), sol_pool_pda, - ctx.accounts.light_system_program.to_account_info(), // none compression_recipient - ctx.accounts.system_program.to_account_info(), - ctx.accounts.light_system_program.to_account_info(), // none cpi_context_account - ctx.accounts.merkle_tree.to_account_info(), // first remaining account + ctx.accounts.get_light_system_program(), // none compression_recipient + ctx.accounts.get_system_program(), + ctx.accounts.get_light_system_program(), // none cpi_context_account + ctx.accounts.get_merkle_tree(), // first remaining account ]; // account_metas take 1k cu @@ -304,8 +335,8 @@ pub fn serialize_mint_to_cpi_instruction_data( } #[inline(never)] -pub fn mint_spl_to_pool_pda<'info>( - ctx: &Context<'_, '_, '_, 'info, MintToInstruction<'info>>, +pub fn mint_spl_to_pool_pda<'info, A: MintToAccounts<'info> + Bumps>( + ctx: &Context<'_, '_, '_, 'info, A>, amounts: &[u64], ) -> Result<()> { let mut mint_amount: u64 = 0; @@ -314,19 +345,39 @@ pub fn mint_spl_to_pool_pda<'info>( .checked_add(*amount) .ok_or(crate::ErrorCode::MintTooLarge)?; } - let pre_token_balance = ctx.accounts.token_pool_pda.amount; - let cpi_accounts = anchor_spl::token::MintTo { - mint: ctx.accounts.mint.to_account_info(), - to: ctx.accounts.token_pool_pda.to_account_info(), - authority: ctx.accounts.authority.to_account_info(), - }; - let cpi_ctx = CpiContext::new(ctx.accounts.token_program.to_account_info(), cpi_accounts); + let pre_token_balance = + TokenAccount::try_deserialize(&mut &ctx.accounts.get_token_pool_pda().data.borrow()[..])? + .amount; + // TODO: differentiate between this and token22 + match ctx.accounts.get_token_program().key() { + spl_token::ID => { + let cpi_ctx = CpiContext::new( + ctx.accounts.get_token_program(), + anchor_spl::token::MintTo { + mint: ctx.accounts.get_mint(), + to: ctx.accounts.get_token_pool_pda(), + authority: ctx.accounts.get_authority(), + }, + ); + anchor_spl::token::mint_to(cpi_ctx, mint_amount)?; + } + token_2022::ID => { + let cpi_ctx = CpiContext::new( + ctx.accounts.get_token_program(), + anchor_spl::token_2022::MintTo { + mint: ctx.accounts.get_mint(), + to: ctx.accounts.get_token_pool_pda(), + authority: ctx.accounts.get_authority(), + }, + ); + anchor_spl::token_2022::mint_to(cpi_ctx, mint_amount)?; + } + _ => unreachable!(""), + } - anchor_spl::token::mint_to(cpi_ctx, mint_amount)?; - let post_token_balance = TokenAccount::try_deserialize( - &mut &ctx.accounts.token_pool_pda.to_account_info().data.borrow()[..], - )? - .amount; + let post_token_balance = + TokenAccount::try_deserialize(&mut &ctx.accounts.get_token_pool_pda().data.borrow()[..])? + .amount; // Guard against unexpected behavior of the SPL token program. if post_token_balance != pre_token_balance + mint_amount { msg!( @@ -340,50 +391,6 @@ pub fn mint_spl_to_pool_pda<'info>( Ok(()) } -#[derive(Accounts)] -pub struct MintToInstruction<'info> { - /// UNCHECKED: only pays fees. - #[account(mut)] - pub fee_payer: Signer<'info>, - /// CHECK: is checked by mint account macro. - pub authority: Signer<'info>, - /// CHECK: - #[account(seeds = [CPI_AUTHORITY_PDA_SEED], bump)] - pub cpi_authority_pda: UncheckedAccount<'info>, - /// CHECK: that authority is mint authority - #[account( - mut, - constraint = mint.mint_authority.unwrap() == authority.key() - @ crate::ErrorCode::InvalidAuthorityMint - )] - pub mint: Account<'info, Mint>, - /// CHECK: this account is checked implictly since a mint to from a mint - /// account to a token account of a different mint will fail - #[account(mut, seeds = [POOL_SEED, &mint.key().to_bytes()],bump)] - pub token_pool_pda: Account<'info, TokenAccount>, - pub token_program: Program<'info, Token>, - pub light_system_program: Program<'info, LightSystemProgram>, - /// CHECK: (different program) checked in account compression program - pub registered_program_pda: UncheckedAccount<'info>, - /// CHECK: (different program) checked in system and account compression - /// programs - pub noop_program: UncheckedAccount<'info>, - /// CHECK: this account in account compression program - #[account(seeds = [CPI_AUTHORITY_PDA_SEED], bump, seeds::program = light_system_program::ID)] - pub account_compression_authority: UncheckedAccount<'info>, - /// CHECK: this account in account compression program - pub account_compression_program: Program<'info, AccountCompression>, - /// CHECK: (different program) will be checked by the system program - #[account(mut)] - pub merkle_tree: UncheckedAccount<'info>, - /// CHECK: (different program) will be checked by the system program - pub self_program: Program<'info, LightCompressedToken>, - pub system_program: Program<'info, System>, - /// CHECK: (different program) will be checked by the system program - #[account(mut)] - pub sol_pool_pda: Option>, -} - pub fn get_token_pool_pda(mint: &Pubkey) -> Pubkey { let seeds = &[POOL_SEED, mint.as_ref()]; let (address, _) = Pubkey::find_program_address(seeds, &crate::ID); @@ -417,6 +424,29 @@ pub mod mint_sdk { } } + pub fn create_create_token_pool_22_instruction( + fee_payer: &Pubkey, + mint: &Pubkey, + ) -> Instruction { + let token_pool_pda = get_token_pool_pda(mint); + let instruction_data = crate::instruction::CreateTokenPool22 {}; + + let accounts = crate::accounts::CreateTokenPoolInstruction22 { + fee_payer: *fee_payer, + token_pool_pda, + system_program: system_program::ID, + mint: *mint, + token_program: anchor_spl::token_2022::ID, + cpi_authority_pda: get_cpi_authority_pda().0, + }; + + Instruction { + program_id: crate::ID, + accounts: accounts.to_account_metas(Some(true)), + data: instruction_data.data(), + } + } + pub fn create_mint_to_instruction( fee_payer: &Pubkey, authority: &Pubkey, @@ -469,6 +499,59 @@ pub mod mint_sdk { data: instruction_data.data(), } } + + pub fn create_mint_to_22_instruction( + fee_payer: &Pubkey, + authority: &Pubkey, + mint: &Pubkey, + merkle_tree: &Pubkey, + amounts: Vec, + public_keys: Vec, + lamports: Option, + ) -> Instruction { + let token_pool_pda = get_token_pool_pda(mint); + + let instruction_data = crate::instruction::MintTo22 { + amounts, + public_keys, + lamports, + }; + let sol_pool_pda = if lamports.is_some() { + Some(get_sol_pool_pda()) + } else { + None + }; + + let accounts = crate::accounts::MintToInstruction22 { + fee_payer: *fee_payer, + authority: *authority, + cpi_authority_pda: get_cpi_authority_pda().0, + mint: *mint, + token_pool_pda, + token_program: anchor_spl::token_2022::ID, + light_system_program: light_system_program::ID, + registered_program_pda: light_system_program::utils::get_registered_program_pda( + &light_system_program::ID, + ), + noop_program: Pubkey::new_from_array( + account_compression::utils::constants::NOOP_PUBKEY, + ), + account_compression_authority: light_system_program::utils::get_cpi_authority_pda( + &light_system_program::ID, + ), + account_compression_program: account_compression::ID, + merkle_tree: *merkle_tree, + self_program: crate::ID, + system_program: system_program::ID, + sol_pool_pda, + }; + + Instruction { + program_id: crate::ID, + accounts: accounts.to_account_metas(Some(true)), + data: instruction_data.data(), + } + } } #[cfg(test)] diff --git a/programs/compressed-token/src/process_transfer.rs b/programs/compressed-token/src/process_transfer.rs index 6bc06d9eff..ff1e0fb02b 100644 --- a/programs/compressed-token/src/process_transfer.rs +++ b/programs/compressed-token/src/process_transfer.rs @@ -2,10 +2,12 @@ use crate::{ constants::{BUMP_CPI_AUTHORITY, NOT_FROZEN, TOKEN_COMPRESSED_ACCOUNT_DISCRIMINATOR}, spl_compression::process_compression_or_decompression, token_data::{AccountState, TokenData}, - ErrorCode, TransferInstruction, + ErrorCode, TokenAccounts, }; use account_compression::utils::constants::CPI_AUTHORITY_PDA_SEED; -use anchor_lang::{prelude::*, solana_program::program_error::ProgramError, AnchorDeserialize}; +use anchor_lang::{ + prelude::*, solana_program::program_error::ProgramError, AnchorDeserialize, Bumps, +}; use light_hasher::Poseidon; use light_heap::{bench_sbf_end, bench_sbf_start}; use light_system_program::{ @@ -33,8 +35,14 @@ use light_utils::hash_to_bn254_field_size_be; /// 4. create_output_compressed_accounts /// 5. Serialize and add token_data data to in compressed_accounts. /// 6. Invoke light_system_program::execute_compressed_transaction. -pub fn process_transfer<'a, 'b, 'c, 'info: 'b + 'c>( - ctx: Context<'a, 'b, 'c, 'info, TransferInstruction<'info>>, +pub fn process_transfer< + 'a, + 'b, + 'c, + 'info: 'b + 'c, + A: InvokeAccounts<'info> + SignerAccounts<'info> + Bumps + TokenAccounts<'info>, +>( + ctx: Context<'a, 'b, 'c, 'info, A>, inputs: Vec, ) -> Result<()> { bench_sbf_start!("t_deserialize"); @@ -49,7 +57,7 @@ pub fn process_transfer<'a, 'b, 'c, 'info: 'b + 'c>( } let (mut compressed_input_accounts, input_token_data, input_lamports) = get_input_compressed_accounts_with_merkle_context_and_check_signer::( - &ctx.accounts.authority.key(), + &ctx.accounts.get_authority().key(), &inputs.delegated_transfer, ctx.remaining_accounts, &inputs.input_token_data_with_context, @@ -90,7 +98,7 @@ pub fn process_transfer<'a, 'b, 'c, 'info: 'b + 'c>( let mut vec = vec![false; inputs.output_compressed_accounts.len()]; if let Some(index) = delegated_transfer.delegate_change_account_index { vec[index as usize] = true; - (Some(vec), Some(ctx.accounts.authority.key())) + (Some(vec), Some(ctx.accounts.get_authority().key())) } else { (None, None) } @@ -156,7 +164,7 @@ pub fn process_transfer<'a, 'b, 'c, 'info: 'b + 'c>( new_len, OutputCompressedAccountWithPackedContext { compressed_account: CompressedAccount { - owner: ctx.accounts.authority.key(), + owner: ctx.accounts.get_authority().key(), lamports: change_lamports, data: None, address: None, @@ -172,9 +180,9 @@ pub fn process_transfer<'a, 'b, 'c, 'info: 'b + 'c>( &output_compressed_accounts, inputs.proof, inputs.cpi_context, - ctx.accounts.cpi_authority_pda.to_account_info(), - ctx.accounts.light_system_program.to_account_info(), - ctx.accounts.self_program.to_account_info(), + ctx.accounts.get_cpi_authority(), + ctx.accounts.get_light_system_program(), + ctx.accounts.get_self_program(), ctx.remaining_accounts, ) } @@ -595,7 +603,7 @@ pub mod transfer_sdk { use std::collections::HashMap; use anchor_lang::{AnchorSerialize, Id, InstructionData, ToAccountMetas}; - use anchor_spl::token::Token; + use anchor_spl::{token::Token, token_2022::Token2022}; use light_system_program::{ invoke::processor::CompressedProof, sdk::compressed_account::{CompressedAccount, MerkleContext, PackedMerkleContext}, @@ -707,6 +715,88 @@ pub mod transfer_sdk { }) } + #[allow(clippy::too_many_arguments)] + pub fn create_transfer_22_instruction( + fee_payer: &Pubkey, + owner: &Pubkey, + input_merkle_context: &[MerkleContext], + output_compressed_accounts: &[TokenTransferOutputData], + root_indices: &[u16], + proof: &Option, + input_token_data: &[TokenData], + input_compressed_accounts: &[CompressedAccount], + mint: Pubkey, + delegate: Option, + is_compress: bool, + compress_or_decompress_amount: Option, + token_pool_pda: Option, + compress_or_decompress_token_account: Option, + sort: bool, + delegate_change_account_index: Option, + lamports_change_account_merkle_tree: Option, + ) -> Result { + let (remaining_accounts, mut inputs_struct) = create_inputs_and_remaining_accounts( + input_token_data, + input_compressed_accounts, + input_merkle_context, + delegate, + output_compressed_accounts, + root_indices, + proof, + mint, + is_compress, + compress_or_decompress_amount, + delegate_change_account_index, + lamports_change_account_merkle_tree, + ); + if sort { + inputs_struct + .output_compressed_accounts + .sort_by_key(|data| data.merkle_tree_index); + } + let remaining_accounts = to_account_metas(remaining_accounts); + let mut inputs = Vec::new(); + CompressedTokenInstructionDataTransfer::serialize(&inputs_struct, &mut inputs) + .map_err(|_| TransferSdkError::SerializationError)?; + + let (cpi_authority_pda, _) = crate::process_transfer::get_cpi_authority_pda(); + let instruction_data = crate::instruction::Transfer22 { inputs }; + let authority = if let Some(delegate) = delegate { + delegate + } else { + *owner + }; + + let accounts = crate::accounts::TransferInstruction22 { + fee_payer: *fee_payer, + authority, + cpi_authority_pda, + light_system_program: light_system_program::ID, + registered_program_pda: light_system_program::utils::get_registered_program_pda( + &light_system_program::ID, + ), + noop_program: Pubkey::new_from_array( + account_compression::utils::constants::NOOP_PUBKEY, + ), + account_compression_authority: light_system_program::utils::get_cpi_authority_pda( + &light_system_program::ID, + ), + account_compression_program: account_compression::ID, + self_program: crate::ID, + token_pool_pda, + compress_or_decompress_token_account, + token_program: token_pool_pda.map(|_| Token2022::id()), + system_program: solana_sdk::system_program::ID, + }; + + Ok(Instruction { + program_id: crate::ID, + accounts: [accounts.to_account_metas(Some(true)), remaining_accounts].concat(), + + data: instruction_data.data(), + }) + } + #[allow(clippy::too_many_arguments)] pub fn create_inputs_and_remaining_accounts_checked( input_token_data: &[TokenData], diff --git a/programs/compressed-token/src/spl_compression.rs b/programs/compressed-token/src/spl_compression.rs index a0401c3c62..ab4c53540f 100644 --- a/programs/compressed-token/src/spl_compression.rs +++ b/programs/compressed-token/src/spl_compression.rs @@ -1,14 +1,18 @@ -use anchor_lang::{prelude::*, solana_program::account_info::AccountInfo}; +use anchor_lang::{prelude::*, solana_program::account_info::AccountInfo, Bumps}; use anchor_spl::token::Transfer; +use light_system_program::sdk::accounts::InvokeAccounts; use crate::{ - process_transfer::get_cpi_signer_seeds, CompressedTokenInstructionDataTransfer, - TransferInstruction, POOL_SEED, + process_transfer::get_cpi_signer_seeds, CompressedTokenInstructionDataTransfer, TokenAccounts, + POOL_SEED, }; -pub fn process_compression_or_decompression<'info>( +pub fn process_compression_or_decompression< + 'info, + A: TokenAccounts<'info> + Bumps + InvokeAccounts<'info>, +>( inputs: &CompressedTokenInstructionDataTransfer, - ctx: &Context<'_, '_, '_, 'info, TransferInstruction<'info>>, + ctx: &Context<'_, '_, '_, 'info, A>, ) -> Result<()> { if inputs.is_compress { compress_spl_tokens(inputs, ctx) @@ -31,15 +35,19 @@ pub fn spl_token_pool_derivation( } } -pub fn decompress_spl_tokens<'info>( +pub fn decompress_spl_tokens<'info, A: TokenAccounts<'info> + Bumps + InvokeAccounts<'info>>( inputs: &CompressedTokenInstructionDataTransfer, - ctx: &Context<'_, '_, '_, 'info, TransferInstruction<'info>>, + ctx: &Context<'_, '_, '_, 'info, A>, ) -> Result<()> { - let recipient = match ctx.accounts.compress_or_decompress_token_account.as_ref() { + let recipient = match ctx + .accounts + .get_compress_or_decompress_token_account() + .as_ref() + { Some(compression_recipient) => compression_recipient.to_account_info(), None => return err!(crate::ErrorCode::DecompressRecipientUndefinedForDecompress), }; - let token_pool_pda = match ctx.accounts.token_pool_pda.as_ref() { + let token_pool_pda = match ctx.accounts.get_token_pool_pda().as_ref() { Some(token_pool_pda) => token_pool_pda.to_account_info(), None => return err!(crate::ErrorCode::CompressedPdaUndefinedForDecompress), }; @@ -49,25 +57,41 @@ pub fn decompress_spl_tokens<'info>( Some(amount) => amount, None => return err!(crate::ErrorCode::DeCompressAmountUndefinedForDecompress), }; - transfer( - &token_pool_pda, - &recipient, - &ctx.accounts.cpi_authority_pda.to_account_info(), - &ctx.accounts - .token_program - .as_ref() - .unwrap() - .to_account_info(), - amount, - ) + match ctx.accounts.get_token_program().unwrap().key() { + spl_token::ID => transfer( + token_pool_pda, + recipient, + ctx.accounts.get_cpi_authority(), + ctx.accounts + .get_token_program() + .as_ref() + .unwrap() + .to_account_info(), + amount, + false, + ), + anchor_spl::token_2022::ID => transfer( + token_pool_pda, + recipient, + ctx.accounts.get_cpi_authority(), + ctx.accounts + .get_token_program() + .as_ref() + .unwrap() + .to_account_info(), + amount, + true, + ), + _ => unreachable!(), + } } -pub fn compress_spl_tokens<'info>( +pub fn compress_spl_tokens<'info, A: TokenAccounts<'info> + Bumps>( inputs: &CompressedTokenInstructionDataTransfer, - ctx: &Context<'_, '_, '_, 'info, TransferInstruction<'info>>, + ctx: &Context<'_, '_, '_, 'info, A>, ) -> Result<()> { - let recipient_token_pool = match ctx.accounts.token_pool_pda.as_ref() { - Some(token_pool_pda) => token_pool_pda.to_account_info(), + let recipient_token_pool = match ctx.accounts.get_token_pool_pda() { + Some(token_pool_pda) => token_pool_pda, None => return err!(crate::ErrorCode::CompressedPdaUndefinedForCompress), }; spl_token_pool_derivation(&inputs.mint, &crate::ID, &recipient_token_pool.key())?; @@ -76,54 +100,100 @@ pub fn compress_spl_tokens<'info>( None => return err!(crate::ErrorCode::DeCompressAmountUndefinedForCompress), }; - transfer_compress( - &ctx.accounts - .compress_or_decompress_token_account - .as_ref() - .unwrap() - .to_account_info(), - &recipient_token_pool, - &ctx.accounts.authority.to_account_info(), - &ctx.accounts - .token_program - .as_ref() - .unwrap() - .to_account_info(), - amount, - ) + match ctx.accounts.get_token_program().unwrap().key() { + spl_token::ID => transfer_compress( + ctx.accounts + .get_compress_or_decompress_token_account() + .as_ref() + .unwrap() + .to_account_info(), + recipient_token_pool, + ctx.accounts.get_cpi_authority(), + ctx.accounts + .get_token_program() + .as_ref() + .unwrap() + .to_account_info(), + amount, + false, + ), + anchor_spl::token_2022::ID => transfer_compress( + ctx.accounts + .get_compress_or_decompress_token_account() + .as_ref() + .unwrap() + .to_account_info(), + recipient_token_pool, + ctx.accounts.get_cpi_authority(), + ctx.accounts + .get_token_program() + .as_ref() + .unwrap() + .to_account_info(), + amount, + true, + ), + _ => unreachable!(""), + } } pub fn transfer<'info>( - from: &AccountInfo<'info>, - to: &AccountInfo<'info>, - authority: &AccountInfo<'info>, - token_program: &AccountInfo<'info>, + from: AccountInfo<'info>, + to: AccountInfo<'info>, + authority: AccountInfo<'info>, + token_program: AccountInfo<'info>, amount: u64, + token_22: bool, ) -> Result<()> { let signer_seeds = get_cpi_signer_seeds(); let signer_seeds_ref = &[&signer_seeds[..]]; - let accounts = Transfer { - from: from.to_account_info(), - to: to.to_account_info(), - authority: authority.to_account_info(), - }; - let cpi_ctx = - CpiContext::new_with_signer(token_program.to_account_info(), accounts, signer_seeds_ref); - anchor_spl::token::transfer(cpi_ctx, amount) + + if token_22 { + let accounts = anchor_spl::token_2022::Transfer { + from, + to, + authority, + }; + let cpi_ctx = CpiContext::new_with_signer( + token_program.to_account_info(), + accounts, + signer_seeds_ref, + ); + anchor_spl::token_2022::transfer(cpi_ctx, amount) + } else { + let accounts = Transfer { + from, + to, + authority, + }; + let cpi_ctx = CpiContext::new_with_signer(token_program, accounts, signer_seeds_ref); + anchor_spl::token::transfer(cpi_ctx, amount) + } } pub fn transfer_compress<'info>( - from: &AccountInfo<'info>, - to: &AccountInfo<'info>, - authority: &AccountInfo<'info>, - token_program: &AccountInfo<'info>, + from: AccountInfo<'info>, + to: AccountInfo<'info>, + authority: AccountInfo<'info>, + token_program: AccountInfo<'info>, amount: u64, + token_22: bool, ) -> Result<()> { - let accounts = Transfer { - from: from.to_account_info(), - to: to.to_account_info(), - authority: authority.to_account_info(), - }; - let cpi_ctx = CpiContext::new(token_program.to_account_info(), accounts); - anchor_spl::token::transfer(cpi_ctx, amount) + if token_22 { + let accounts = anchor_spl::token_2022::Transfer { + from, + to, + authority, + }; + let cpi_ctx = CpiContext::new(token_program.to_account_info(), accounts); + anchor_spl::token_2022::transfer(cpi_ctx, amount) + } else { + let accounts = Transfer { + from, + to, + authority, + }; + let cpi_ctx = CpiContext::new(token_program.to_account_info(), accounts); + anchor_spl::token::transfer(cpi_ctx, amount) + } } diff --git a/test-programs/compressed-token-test/tests/test.rs b/test-programs/compressed-token-test/tests/test.rs index a158a6fdd4..24bbb5ad8b 100644 --- a/test-programs/compressed-token-test/tests/test.rs +++ b/test-programs/compressed-token-test/tests/test.rs @@ -22,19 +22,19 @@ use light_system_program::{ sdk::compressed_account::{CompressedAccountWithMerkleContext, MerkleContext}, }; use light_test_utils::rpc::test_rpc::ProgramTestRpcConnection; -use light_test_utils::spl::approve_test; -use light_test_utils::spl::burn_test; -use light_test_utils::spl::create_burn_test_instruction; use light_test_utils::spl::freeze_test; use light_test_utils::spl::mint_tokens_helper_with_lamports; use light_test_utils::spl::mint_wrapped_sol; use light_test_utils::spl::revoke_test; use light_test_utils::spl::thaw_test; use light_test_utils::spl::BurnInstructionMode; +use light_test_utils::spl::{approve_test, create_mint_22_helper}; +use light_test_utils::spl::{burn_test, mint_tokens_22_helper_with_lamports}; use light_test_utils::spl::{ compress_test, compressed_transfer_test, create_mint_helper, create_token_account, decompress_test, mint_tokens_helper, }; +use light_test_utils::spl::{compressed_transfer_22_test, create_burn_test_instruction}; use light_test_utils::{ airdrop_lamports, assert_rpc_error, create_account_instruction, Indexer, RpcConnection, RpcError, TokenDataWithContext, @@ -270,6 +270,32 @@ async fn test_mint_to(amounts: Vec, iterations: usize, lamports: Option::init_from_env(&payer, &env, None).await; + let mint = create_mint_22_helper(&mut rpc, &payer).await; + mint_tokens_22_helper_with_lamports( + &mut rpc, + &mut test_indexer, + &merkle_tree_pubkey, + &payer, + &mint, + vec![1u64; 25].clone(), + vec![payer.pubkey(); 25].clone(), + None, + true, + ) + .await; +} +#[tokio::test] +async fn test_22_transfer() { + perform_transfer_22_test(1, 1, 12412, true).await; +} + #[tokio::test] async fn test_1_mint_to() { test_mint_to(vec![10000], 1, Some(1_000_000)).await @@ -775,6 +801,9 @@ async fn test_8_transfer() { /// Creates inputs compressed accounts with amount tokens each /// Transfers all tokens from inputs compressed accounts evenly distributed to outputs compressed accounts async fn perform_transfer_test(inputs: usize, outputs: usize, amount: u64) { + perform_transfer_22_test(inputs, outputs, amount, false).await; +} +async fn perform_transfer_22_test(inputs: usize, outputs: usize, amount: u64, token_22: bool) { let (mut rpc, env) = setup_test_programs_with_accounts(None).await; let payer = rpc.get_payer().insecure_clone(); let merkle_tree_pubkey = env.merkle_tree_pubkey; @@ -787,9 +816,13 @@ async fn perform_transfer_test(inputs: usize, outputs: usize, amount: u64) { }), ) .await; - let mint = create_mint_helper(&mut rpc, &payer).await; + let mint = if token_22 { + create_mint_22_helper(&mut rpc, &payer).await + } else { + create_mint_helper(&mut rpc, &payer).await + }; let sender = Keypair::new(); - mint_tokens_helper_with_lamports( + mint_tokens_22_helper_with_lamports( &mut rpc, &mut test_indexer, &merkle_tree_pubkey, @@ -798,6 +831,7 @@ async fn perform_transfer_test(inputs: usize, outputs: usize, amount: u64) { vec![amount; inputs], vec![sender.pubkey(); inputs], Some(1_000_000), + token_22, ) .await; let mut recipients = Vec::new(); @@ -810,7 +844,7 @@ async fn perform_transfer_test(inputs: usize, outputs: usize, amount: u64) { let rest_amount = (amount * inputs as u64) % outputs as u64; let mut output_amounts = vec![equal_amount; outputs - 1]; output_amounts.push(equal_amount + rest_amount); - compressed_transfer_test( + compressed_transfer_22_test( &payer, &mut rpc, &mut test_indexer, @@ -824,6 +858,7 @@ async fn perform_transfer_test(inputs: usize, outputs: usize, amount: u64) { None, false, None, + token_22, ) .await; } diff --git a/test-utils/Cargo.toml b/test-utils/Cargo.toml index 05737bb4d6..94e50c581b 100644 --- a/test-utils/Cargo.toml +++ b/test-utils/Cargo.toml @@ -46,6 +46,7 @@ log = "0.4" serde = { version = "1.0.197", features = ["derive"] } async-trait = "0.1.82" light-client = { workspace = true } +spl-token-2022 = "3.0.0" [dev-dependencies] rand = "0.8" diff --git a/test-utils/src/spl.rs b/test-utils/src/spl.rs index 1f2a51c240..434310c266 100644 --- a/test-utils/src/spl.rs +++ b/test-utils/src/spl.rs @@ -1,6 +1,10 @@ use anchor_spl::token::{Mint, TokenAccount}; use forester_utils::create_account_instruction; use forester_utils::indexer::{Indexer, TokenDataWithContext}; +use light_compressed_token::mint_sdk::{ + create_create_token_pool_22_instruction, create_mint_to_22_instruction, +}; +use light_compressed_token::process_transfer::transfer_sdk::create_transfer_22_instruction; use light_compressed_token::{ burn::sdk::{create_burn_instruction, CreateBurnInstructionInputs}, delegation::sdk::{ @@ -68,16 +72,53 @@ pub async fn mint_tokens_helper_with_lamports>( recipients: Vec, lamports: Option, ) { - let payer_pubkey = mint_authority.pubkey(); - let instruction = create_mint_to_instruction( - &payer_pubkey, - &payer_pubkey, - mint, + mint_tokens_22_helper_with_lamports( + rpc, + test_indexer, merkle_tree_pubkey, - amounts.clone(), - recipients.clone(), + mint_authority, + mint, + amounts, + recipients, lamports, - ); + false, + ) + .await; +} +#[allow(clippy::too_many_arguments)] +pub async fn mint_tokens_22_helper_with_lamports>( + rpc: &mut R, + test_indexer: &mut I, + merkle_tree_pubkey: &Pubkey, + mint_authority: &Keypair, + mint: &Pubkey, + amounts: Vec, + recipients: Vec, + lamports: Option, + token_22: bool, +) { + let payer_pubkey = mint_authority.pubkey(); + let instruction = if token_22 { + create_mint_to_22_instruction( + &payer_pubkey, + &payer_pubkey, + mint, + merkle_tree_pubkey, + amounts.clone(), + recipients.clone(), + lamports, + ) + } else { + create_mint_to_instruction( + &payer_pubkey, + &payer_pubkey, + mint, + merkle_tree_pubkey, + amounts.clone(), + recipients.clone(), + lamports, + ) + }; let output_merkle_tree_accounts = test_indexer.get_state_merkle_tree_accounts(&vec![*merkle_tree_pubkey; amounts.len()]); @@ -182,6 +223,24 @@ pub async fn create_mint_helper(rpc: &mut R, payer: &Keypair) mint.pubkey() } +pub async fn create_mint_22_helper(rpc: &mut R, payer: &Keypair) -> Pubkey { + let payer_pubkey = payer.pubkey(); + let rent = rpc + .get_minimum_balance_for_rent_exemption(Mint::LEN) + .await + .unwrap(); + let mint = Keypair::new(); + + let (instructions, pool) = + create_initialize_mint_22_instructions(&payer_pubkey, &payer_pubkey, rent, 2, &mint, true); + + rpc.create_and_send_transaction(&instructions, &payer_pubkey, &[payer, &mint]) + .await + .unwrap(); + assert_create_mint(rpc, &payer_pubkey, &mint.pubkey(), &pool).await; + mint.pubkey() +} + pub async fn mint_wrapped_sol( rpc: &mut R, payer: &Keypair, @@ -199,7 +258,6 @@ pub async fn mint_wrapped_sol( rpc.create_and_send_transaction(&[transfer_ix, sync_native_ix], &payer.pubkey(), &[payer]) .await } - pub fn create_initialize_mint_instructions( payer: &Pubkey, authority: &Pubkey, @@ -207,22 +265,53 @@ pub fn create_initialize_mint_instructions( decimals: u8, mint_keypair: &Keypair, ) -> ([Instruction; 4], Pubkey) { + create_initialize_mint_22_instructions(payer, authority, rent, decimals, mint_keypair, false) +} + +pub fn create_initialize_mint_22_instructions( + payer: &Pubkey, + authority: &Pubkey, + rent: u64, + decimals: u8, + mint_keypair: &Keypair, + token_22: bool, +) -> ([Instruction; 4], Pubkey) { + let program_id = if token_22 { + anchor_spl::token_2022::ID + } else { + spl_token::ID + }; let account_create_ix = - create_account_instruction(payer, Mint::LEN, rent, &spl_token::ID, Some(mint_keypair)); + create_account_instruction(payer, Mint::LEN, rent, &program_id, Some(mint_keypair)); let mint_pubkey = mint_keypair.pubkey(); - let create_mint_instruction = initialize_mint( - &spl_token::ID, - &mint_keypair.pubkey(), - authority, - Some(authority), - decimals, - ) - .unwrap(); + let create_mint_instruction = if token_22 { + spl_token_2022::instruction::initialize_mint( + &program_id, + &mint_keypair.pubkey(), + authority, + Some(authority), + decimals, + ) + .unwrap() + } else { + initialize_mint( + &program_id, + &mint_keypair.pubkey(), + authority, + Some(authority), + decimals, + ) + .unwrap() + }; let transfer_ix = anchor_lang::solana_program::system_instruction::transfer(payer, &mint_pubkey, rent); - let instruction = create_create_token_pool_instruction(payer, &mint_pubkey); + let instruction = if token_22 { + create_create_token_pool_22_instruction(payer, &mint_pubkey) + } else { + create_create_token_pool_instruction(payer, &mint_pubkey) + }; let pool_pubkey = get_token_pool_pda(&mint_pubkey); ( [ @@ -242,25 +331,49 @@ pub async fn create_token_account( mint: &Pubkey, account_keypair: &Keypair, owner: &Keypair, +) -> Result<(), BanksClientError> { + create_token_22_account(rpc, mint, account_keypair, owner, false).await +} +pub async fn create_token_22_account( + rpc: &mut R, + mint: &Pubkey, + account_keypair: &Keypair, + owner: &Keypair, + token_22: bool, ) -> Result<(), BanksClientError> { let rent = rpc .get_minimum_balance_for_rent_exemption(TokenAccount::LEN) .await .unwrap(); + let program_id = if token_22 { + spl_token_2022::ID + } else { + spl_token::ID + }; let account_create_ix = create_account_instruction( &owner.pubkey(), TokenAccount::LEN, rent, - &spl_token::ID, + &program_id, Some(account_keypair), ); - let instruction = spl_token::instruction::initialize_account( - &spl_token::ID, - &account_keypair.pubkey(), - mint, - &owner.pubkey(), - ) - .unwrap(); + let instruction = if token_22 { + spl_token_2022::instruction::initialize_account( + &program_id, + &account_keypair.pubkey(), + mint, + &owner.pubkey(), + ) + .unwrap() + } else { + spl_token::instruction::initialize_account( + &program_id, + &account_keypair.pubkey(), + mint, + &owner.pubkey(), + ) + .unwrap() + }; rpc.create_and_send_transaction( &[account_create_ix, instruction], &owner.pubkey(), @@ -273,6 +386,41 @@ pub async fn create_token_account( #[allow(clippy::too_many_arguments)] pub async fn compressed_transfer_test>( + payer: &Keypair, + rpc: &mut R, + test_indexer: &mut I, + mint: &Pubkey, + from: &Keypair, + recipients: &[Pubkey], + amounts: &[u64], + lamports: Option>>, + input_compressed_accounts: &[TokenDataWithContext], + output_merkle_tree_pubkeys: &[Pubkey], + delegate_change_account_index: Option, + delegate_is_signer: bool, + transaction_params: Option, +) { + compressed_transfer_22_test( + payer, + rpc, + test_indexer, + mint, + from, + recipients, + amounts, + lamports, + input_compressed_accounts, + output_merkle_tree_pubkeys, + delegate_change_account_index, + delegate_is_signer, + transaction_params, + false, + ) + .await; +} + +#[allow(clippy::too_many_arguments)] +pub async fn compressed_transfer_22_test>( payer: &Keypair, rpc: &mut R, test_indexer: &mut I, @@ -286,6 +434,7 @@ pub async fn compressed_transfer_test>( delegate_change_account_index: Option, delegate_is_signer: bool, transaction_params: Option, + token_22: bool, ) { if recipients.len() != amounts.len() && amounts.len() != output_merkle_tree_pubkeys.len() { println!("{:?}", recipients); @@ -376,30 +525,57 @@ pub async fn compressed_transfer_test>( None }; let authority_signer = if delegate_is_signer { payer } else { from }; - let instruction = create_transfer_instruction( - &payer.pubkey(), - &authority_signer.pubkey(), // authority - &input_merkle_tree_context, - &output_compressed_accounts, - &proof_rpc_result.root_indices, - &Some(proof_rpc_result.proof), - &input_compressed_account_token_data, // input_token_data - &input_compressed_accounts - .iter() - .map(|x| &x.compressed_account.compressed_account) - .cloned() - .collect::>(), - *mint, - delegate_pubkey, // owner_if_delegate_change_account_index - false, // is_compress - None, // compression_amount - None, // token_pool_pda - None, // compress_or_decompress_token_account - true, - delegate_change_account_index, - None, - ) - .unwrap(); + let instruction = if token_22 { + create_transfer_22_instruction( + &payer.pubkey(), + &authority_signer.pubkey(), // authority + &input_merkle_tree_context, + &output_compressed_accounts, + &proof_rpc_result.root_indices, + &Some(proof_rpc_result.proof), + &input_compressed_account_token_data, // input_token_data + &input_compressed_accounts + .iter() + .map(|x| &x.compressed_account.compressed_account) + .cloned() + .collect::>(), + *mint, + delegate_pubkey, // owner_if_delegate_change_account_index + false, // is_compress + None, // compression_amount + None, // token_pool_pda + None, // compress_or_decompress_token_account + true, + delegate_change_account_index, + None, + ) + .unwrap() + } else { + create_transfer_instruction( + &payer.pubkey(), + &authority_signer.pubkey(), // authority + &input_merkle_tree_context, + &output_compressed_accounts, + &proof_rpc_result.root_indices, + &Some(proof_rpc_result.proof), + &input_compressed_account_token_data, // input_token_data + &input_compressed_accounts + .iter() + .map(|x| &x.compressed_account.compressed_account) + .cloned() + .collect::>(), + *mint, + delegate_pubkey, // owner_if_delegate_change_account_index + false, // is_compress + None, // compression_amount + None, // token_pool_pda + None, // compress_or_decompress_token_account + true, + delegate_change_account_index, + None, + ) + .unwrap() + }; let sum_input_lamports = input_compressed_accounts .iter() .map(|x| &x.compressed_account.compressed_account.lamports)