From fa6d62299e5ff8d315bf135ad27bf2486930eb6b Mon Sep 17 00:00:00 2001 From: ananas Date: Thu, 15 Jan 2026 20:15:00 +0000 Subject: [PATCH 1/3] refactor: store mint signer in compressed mint struct --- Cargo.lock | 1 + .../mint_action/decompress_mint.rs | 2 - .../mint_action/instruction_data.rs | 5 ++- .../src/state/mint/compressed_mint.rs | 37 ++++++++++++++++--- .../src/state/mint/zero_copy.rs | 5 ++- .../token-interface/tests/compressed_mint.rs | 26 ++++++++----- .../tests/cross_deserialization.rs | 5 ++- .../tests/mint_borsh_zero_copy.rs | 36 +++++++++++------- .../tests/mint/cpi_context.rs | 21 ++++++----- .../tests/mint/functional.rs | 16 ++++---- program-tests/utils/src/mint_assert.rs | 15 +++++++- programs/compressed-token/anchor/src/lib.rs | 2 + .../compressed_token/mint_action/accounts.rs | 7 ++-- .../mint_action/actions/create_mint.rs | 22 ++++++++--- .../mint_action/actions/decompress_mint.rs | 15 ++++---- .../mint_action/actions/process_actions.rs | 4 -- .../mint_action/mint_input.rs | 2 +- .../mint_action/mint_output.rs | 2 +- .../compressed-token/program/tests/mint.rs | 14 +++++-- .../program/tests/mint_action.rs | 3 +- .../token-client/src/actions/mint_action.rs | 11 ++---- .../src/instructions/mint_action.rs | 17 +++------ .../v2/create_compressed_mint/instruction.rs | 14 ++++--- sdk-libs/token-sdk/src/token/create_mint.rs | 13 ++++--- .../token-sdk/src/token/decompress_mint.rs | 7 ++-- .../tests/basic_test.rs | 3 +- .../tests/basic_test.rs | 3 +- .../tests/multi_account_tests.rs | 3 +- sdk-tests/sdk-token-test/Cargo.toml | 1 + sdk-tests/sdk-token-test/tests/ctoken_pda.rs | 5 ++- sdk-tests/sdk-token-test/tests/pda_ctoken.rs | 5 ++- .../sdk-token-test/tests/test_4_transfer2.rs | 15 ++++++-- .../tests/test_compress_full_and_close.rs | 7 ++-- 33 files changed, 213 insertions(+), 131 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index df88d28c3b..e465ced71e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6155,6 +6155,7 @@ dependencies = [ "anchor-lang", "anchor-spl 0.31.1 (registry+https://github.com/rust-lang/crates.io-index)", "arrayvec", + "borsh 0.10.4", "light-batched-merkle-tree", "light-client", "light-compressed-account", diff --git a/program-libs/token-interface/src/instructions/mint_action/decompress_mint.rs b/program-libs/token-interface/src/instructions/mint_action/decompress_mint.rs index 6940ff4ed8..2513b3143d 100644 --- a/program-libs/token-interface/src/instructions/mint_action/decompress_mint.rs +++ b/program-libs/token-interface/src/instructions/mint_action/decompress_mint.rs @@ -10,8 +10,6 @@ use crate::{AnchorDeserialize, AnchorSerialize}; #[repr(C)] #[derive(Debug, Clone, Copy, AnchorSerialize, AnchorDeserialize, ZeroCopy)] pub struct DecompressMintAction { - /// PDA bump for CMint account verification - pub cmint_bump: u8, /// Rent payment in epochs (prepaid). pub rent_payment: u8, /// Lamports allocated for future write operations (top-up per write). diff --git a/program-libs/token-interface/src/instructions/mint_action/instruction_data.rs b/program-libs/token-interface/src/instructions/mint_action/instruction_data.rs index 8cb4b90da8..571c151be6 100644 --- a/program-libs/token-interface/src/instructions/mint_action/instruction_data.rs +++ b/program-libs/token-interface/src/instructions/mint_action/instruction_data.rs @@ -200,9 +200,10 @@ impl<'a> TryFrom<&ZCompressedMintInstructionData<'a>> for CompressedMint { version: instruction_data.metadata.version, cmint_decompressed: instruction_data.metadata.cmint_decompressed != 0, mint: instruction_data.metadata.mint, - compressed_address: instruction_data.metadata.compressed_address, + mint_signer: instruction_data.metadata.mint_signer, + bump: instruction_data.metadata.bump, }, - reserved: [0u8; 17], + reserved: [0u8; 16], account_type: crate::state::mint::ACCOUNT_TYPE_MINT, compression: CompressionInfo::default(), extensions, diff --git a/program-libs/token-interface/src/state/mint/compressed_mint.rs b/program-libs/token-interface/src/state/mint/compressed_mint.rs index 2c42def3d7..e9df255c6f 100644 --- a/program-libs/token-interface/src/state/mint/compressed_mint.rs +++ b/program-libs/token-interface/src/state/mint/compressed_mint.rs @@ -1,5 +1,5 @@ use borsh::{BorshDeserialize, BorshSerialize}; -use light_compressed_account::Pubkey; +use light_compressed_account::{address::derive_address, Pubkey}; use light_compressible::compression_info::CompressionInfo; use light_hasher::{sha256::Sha256BE, Hasher}; use light_zero_copy::{ZeroCopy, ZeroCopyMut}; @@ -8,7 +8,8 @@ use pinocchio::account_info::AccountInfo; use solana_msg::msg; use crate::{ - state::ExtensionStruct, AnchorDeserialize, AnchorSerialize, TokenError, LIGHT_TOKEN_PROGRAM_ID, + state::ExtensionStruct, AnchorDeserialize, AnchorSerialize, TokenError, CMINT_ADDRESS_TREE, + LIGHT_TOKEN_PROGRAM_ID, }; /// AccountType::Mint discriminator value @@ -20,7 +21,7 @@ pub struct CompressedMint { pub base: BaseMint, pub metadata: CompressedMintMetadata, /// Reserved bytes for T22 layout compatibility (padding to reach byte 165) - pub reserved: [u8; 17], + pub reserved: [u8; 16], /// Account type discriminator at byte 165 (1 = Mint, 2 = Account) pub account_type: u8, /// Compression info embedded directly in the mint @@ -33,7 +34,7 @@ impl Default for CompressedMint { Self { base: BaseMint::default(), metadata: CompressedMintMetadata::default(), - reserved: [0u8; 17], + reserved: [0u8; 16], account_type: ACCOUNT_TYPE_MINT, compression: CompressionInfo::default(), extensions: None, @@ -74,8 +75,32 @@ pub struct CompressedMintMetadata { pub cmint_decompressed: bool, /// Pda with seed address of compressed mint pub mint: Pubkey, - /// Address of the compressed account the mint is stored in. - pub compressed_address: [u8; 32], + /// Signer pubkey used to derive the compressed address + pub mint_signer: Pubkey, + /// Bump seed used for compressed address derivation + pub bump: u8, +} + +impl CompressedMintMetadata { + /// Derives the compressed address from mint PDA, CMINT_ADDRESS_TREE and LIGHT_TOKEN_PROGRAM_ID + pub fn compressed_address(&self) -> [u8; 32] { + derive_address( + self.mint.array_ref(), + &CMINT_ADDRESS_TREE, + &LIGHT_TOKEN_PROGRAM_ID, + ) + } +} + +impl ZCompressedMintMetadata<'_> { + /// Derives the compressed address from mint PDA, CMINT_ADDRESS_TREE and LIGHT_TOKEN_PROGRAM_ID + pub fn compressed_address(&self) -> [u8; 32] { + derive_address( + self.mint.array_ref(), + &CMINT_ADDRESS_TREE, + &LIGHT_TOKEN_PROGRAM_ID, + ) + } } impl CompressedMint { diff --git a/program-libs/token-interface/src/state/mint/zero_copy.rs b/program-libs/token-interface/src/state/mint/zero_copy.rs index 7f8068bd63..4fe8b158b3 100644 --- a/program-libs/token-interface/src/state/mint/zero_copy.rs +++ b/program-libs/token-interface/src/state/mint/zero_copy.rs @@ -45,7 +45,7 @@ struct CompressedMintZeroCopyMeta { // CompressedMintMetadata pub metadata: CompressedMintMetadata, /// Reserved bytes for T22 layout compatibility (padding to reach byte 165) - pub reserved: [u8; 17], + pub reserved: [u8; 16], /// Account type discriminator at byte 165 (1 = Mint, 2 = Account) pub account_type: u8, /// Compression info embedded directly in the mint @@ -427,7 +427,8 @@ impl ZCompressedMintMut<'_> { self.base.metadata.version = ix_data.metadata.version; self.base.metadata.mint = ix_data.metadata.mint; self.base.metadata.cmint_decompressed = if cmint_decompressed { 1 } else { 0 }; - self.base.metadata.compressed_address = ix_data.metadata.compressed_address; + self.base.metadata.mint_signer = ix_data.metadata.mint_signer; + self.base.metadata.bump = ix_data.metadata.bump; // Set base fields self.base.supply = ix_data.supply; diff --git a/program-libs/token-interface/tests/compressed_mint.rs b/program-libs/token-interface/tests/compressed_mint.rs index 313eeb1f56..d85bf0d0a6 100644 --- a/program-libs/token-interface/tests/compressed_mint.rs +++ b/program-libs/token-interface/tests/compressed_mint.rs @@ -78,9 +78,10 @@ fn generate_random_compressed_mint(rng: &mut impl Rng, with_extensions: bool) -> version: 3, mint, cmint_decompressed: rng.gen_bool(0.5), - compressed_address: rng.gen(), + mint_signer: Pubkey::from(rng.gen::<[u8; 32]>()), + bump: rng.gen(), }, - reserved: [0u8; 17], + reserved: [0u8; 16], account_type: ACCOUNT_TYPE_MINT, compression: CompressionInfo::default(), extensions, @@ -148,7 +149,8 @@ fn test_compressed_mint_borsh_zerocopy_compatibility() { } else { 0 }; - zc_mint.base.metadata.compressed_address = original_mint.metadata.compressed_address; + zc_mint.base.metadata.mint_signer = original_mint.metadata.mint_signer; + zc_mint.base.metadata.bump = original_mint.metadata.bump; // account_type is already set in new_zero_copy // Set compression fields zc_mint.base.compression.config_account_version = @@ -260,9 +262,10 @@ fn test_compressed_mint_edge_cases() { version: 3, mint: Pubkey::from([0xff; 32]), cmint_decompressed: false, - compressed_address: [0u8; 32], + mint_signer: Pubkey::from([0u8; 32]), + bump: 0, }, - reserved: [0u8; 17], + reserved: [0u8; 16], account_type: ACCOUNT_TYPE_MINT, compression: CompressionInfo::default(), extensions: None, @@ -294,7 +297,8 @@ fn test_compressed_mint_edge_cases() { zc_mint.base.metadata.version = mint_no_auth.metadata.version; zc_mint.base.metadata.mint = mint_no_auth.metadata.mint; zc_mint.base.metadata.cmint_decompressed = 0; - zc_mint.base.metadata.compressed_address = mint_no_auth.metadata.compressed_address; + zc_mint.base.metadata.mint_signer = mint_no_auth.metadata.mint_signer; + zc_mint.base.metadata.bump = mint_no_auth.metadata.bump; // account_type is already set in new_zero_copy // Set compression fields zc_mint.base.compression.config_account_version = @@ -339,9 +343,10 @@ fn test_compressed_mint_edge_cases() { version: 255, mint: Pubkey::from([0xbb; 32]), cmint_decompressed: true, - compressed_address: [0xcc; 32], + mint_signer: Pubkey::from([0xcc; 32]), + bump: 255, }, - reserved: [0u8; 17], + reserved: [0u8; 16], account_type: ACCOUNT_TYPE_MINT, compression: CompressionInfo::default(), extensions: None, @@ -367,9 +372,10 @@ fn test_base_mint_in_compressed_mint_spl_format() { version: 3, mint: Pubkey::from([3; 32]), cmint_decompressed: false, - compressed_address: [4u8; 32], + mint_signer: Pubkey::from([4u8; 32]), + bump: 255, }, - reserved: [0u8; 17], + reserved: [0u8; 16], account_type: ACCOUNT_TYPE_MINT, compression: CompressionInfo::default(), extensions: None, diff --git a/program-libs/token-interface/tests/cross_deserialization.rs b/program-libs/token-interface/tests/cross_deserialization.rs index 911d9f7d89..c454e050df 100644 --- a/program-libs/token-interface/tests/cross_deserialization.rs +++ b/program-libs/token-interface/tests/cross_deserialization.rs @@ -29,9 +29,10 @@ fn create_test_cmint() -> CompressedMint { version: 3, mint: Pubkey::new_from_array([2; 32]), cmint_decompressed: false, - compressed_address: [5u8; 32], + mint_signer: Pubkey::new_from_array([5u8; 32]), + bump: 255, }, - reserved: [0u8; 17], + reserved: [0u8; 16], account_type: ACCOUNT_TYPE_MINT, compression: CompressionInfo { config_account_version: 1, diff --git a/program-libs/token-interface/tests/mint_borsh_zero_copy.rs b/program-libs/token-interface/tests/mint_borsh_zero_copy.rs index 0e4d8135a5..8551d1d1a3 100644 --- a/program-libs/token-interface/tests/mint_borsh_zero_copy.rs +++ b/program-libs/token-interface/tests/mint_borsh_zero_copy.rs @@ -103,9 +103,10 @@ fn generate_random_mint() -> CompressedMint { rng.fill(&mut bytes); Pubkey::from(bytes) }, - compressed_address: rng.gen(), + mint_signer: Pubkey::from(rng.gen::<[u8; 32]>()), + bump: rng.gen(), }, - reserved: [0u8; 17], + reserved: [0u8; 16], account_type: ACCOUNT_TYPE_MINT, compression: CompressionInfo::default(), extensions, @@ -166,7 +167,8 @@ fn compare_mint_borsh_vs_zero_copy(original: &CompressedMint, borsh_bytes: &[u8] version: zc_mint.base.metadata.version, cmint_decompressed: zc_mint.base.metadata.cmint_decompressed != 0, mint: zc_mint.base.metadata.mint, - compressed_address: zc_mint.base.metadata.compressed_address, + mint_signer: zc_mint.base.metadata.mint_signer, + bump: zc_mint.base.metadata.bump, }, reserved: *zc_mint.base.reserved, account_type: zc_mint.base.account_type, @@ -191,7 +193,8 @@ fn compare_mint_borsh_vs_zero_copy(original: &CompressedMint, borsh_bytes: &[u8] version: zc_mint_mut.base.metadata.version, cmint_decompressed: zc_mint_mut.base.metadata.cmint_decompressed != 0, mint: zc_mint_mut.base.metadata.mint, - compressed_address: zc_mint_mut.base.metadata.compressed_address, + mint_signer: zc_mint_mut.base.metadata.mint_signer, + bump: zc_mint_mut.base.metadata.bump, }, reserved: *zc_mint_mut.base.reserved, account_type: *zc_mint_mut.base.account_type, @@ -255,9 +258,10 @@ fn generate_mint_with_extensions() -> CompressedMint { version: 3, cmint_decompressed: rng.gen_bool(0.5), mint: Pubkey::from(rng.gen::<[u8; 32]>()), - compressed_address: rng.gen(), + mint_signer: Pubkey::from(rng.gen::<[u8; 32]>()), + bump: rng.gen(), }, - reserved: [0u8; 17], + reserved: [0u8; 16], account_type: ACCOUNT_TYPE_MINT, compression: CompressionInfo::default(), extensions: Some(vec![ExtensionStruct::TokenMetadata(token_metadata)]), @@ -290,9 +294,10 @@ fn test_mint_extension_edge_cases() { version: 3, cmint_decompressed: false, mint: Pubkey::from([2u8; 32]), - compressed_address: [0u8; 32], + mint_signer: Pubkey::from([0u8; 32]), + bump: 0, }, - reserved: [0u8; 17], + reserved: [0u8; 16], account_type: ACCOUNT_TYPE_MINT, compression: CompressionInfo::default(), extensions: Some(vec![ExtensionStruct::TokenMetadata(TokenMetadata { @@ -320,9 +325,10 @@ fn test_mint_extension_edge_cases() { version: 3, cmint_decompressed: true, mint: Pubkey::from([0xbbu8; 32]), - compressed_address: [0xddu8; 32], + mint_signer: Pubkey::from([0xddu8; 32]), + bump: 255, }, - reserved: [0u8; 17], + reserved: [0u8; 16], account_type: ACCOUNT_TYPE_MINT, compression: CompressionInfo::default(), extensions: Some(vec![ExtensionStruct::TokenMetadata(TokenMetadata { @@ -363,9 +369,10 @@ fn test_mint_extension_edge_cases() { version: 3, cmint_decompressed: false, mint: Pubkey::from([4u8; 32]), - compressed_address: [5u8; 32], + mint_signer: Pubkey::from([5u8; 32]), + bump: 255, }, - reserved: [0u8; 17], + reserved: [0u8; 16], account_type: ACCOUNT_TYPE_MINT, compression: CompressionInfo::default(), extensions: Some(vec![ExtensionStruct::TokenMetadata(TokenMetadata { @@ -393,9 +400,10 @@ fn test_mint_extension_edge_cases() { version: 3, cmint_decompressed: true, mint: Pubkey::from([7u8; 32]), - compressed_address: [8u8; 32], + mint_signer: Pubkey::from([8u8; 32]), + bump: 255, }, - reserved: [0u8; 17], + reserved: [0u8; 16], account_type: ACCOUNT_TYPE_MINT, compression: CompressionInfo::default(), extensions: None, diff --git a/program-tests/compressed-token-test/tests/mint/cpi_context.rs b/program-tests/compressed-token-test/tests/mint/cpi_context.rs index 63bce9a8f3..7949cee5ce 100644 --- a/program-tests/compressed-token-test/tests/mint/cpi_context.rs +++ b/program-tests/compressed-token-test/tests/mint/cpi_context.rs @@ -71,6 +71,7 @@ async fn test_setup() -> TestSetup { let (spl_mint_pda, _) = find_mint_address(&mint_seed.pubkey()); // 3. Build compressed mint inputs + let (_, bump) = find_mint_address(&mint_seed.pubkey()); let compressed_mint_inputs = CompressedMintWithContext { leaf_index: 0, prove_by_index: false, @@ -83,7 +84,8 @@ async fn test_setup() -> TestSetup { version: 3, cmint_decompressed: false, mint: spl_mint_pda.into(), - compressed_address: compressed_mint_address, + mint_signer: mint_seed.pubkey().into(), + bump, }, mint_authority: Some(mint_authority.pubkey().into()), freeze_authority: Some(freeze_authority.into()), @@ -330,13 +332,13 @@ async fn test_write_to_cpi_context_invalid_compressed_address() { output_queue_index, } = test_setup().await; - // Swap the compressed address to a random one (this should fail validation) - // Keep the correct address_tree_pubkey but provide wrong address - let invalid_compressed_address = [42u8; 32]; + // Swap the mint_signer to an invalid one (this should fail validation) + // The compressed address will be derived from the invalid mint_signer + let invalid_mint_signer = light_compressed_account::Pubkey::new_from_array([42u8; 32]); - // Build instruction data with invalid compressed address in metadata + // Build instruction data with invalid mint_signer in metadata let mut invalid_mint = compressed_mint_inputs.mint.clone().unwrap(); - invalid_mint.metadata.compressed_address = invalid_compressed_address; + invalid_mint.metadata.mint_signer = invalid_mint_signer; let instruction_data = MintActionCompressedInstructionData::new_mint( compressed_mint_inputs.root_index, @@ -402,9 +404,9 @@ async fn test_write_to_cpi_context_invalid_compressed_address() { ) .await; - // Assert that the transaction failed with MintActionInvalidCompressedMintAddress error - // Error code 6103 = MintActionInvalidCompressedMintAddress - assert_rpc_error(result, 0, 6103).unwrap(); + // Assert that the transaction failed with MintActionInvalidMintSigner error + // Error code 6171 = MintActionInvalidMintSigner (mint_signer mismatch is caught before compressed address validation) + assert_rpc_error(result, 0, 6171).unwrap(); } #[tokio::test] @@ -710,7 +712,6 @@ async fn test_write_to_cpi_context_decompress_mint_action_fails() { compressed_mint_inputs.mint.clone().unwrap(), ) .with_decompress_mint(DecompressMintAction { - cmint_bump: 255, rent_payment: 2, write_top_up: 1000, }) diff --git a/program-tests/compressed-token-test/tests/mint/functional.rs b/program-tests/compressed-token-test/tests/mint/functional.rs index 18352d8927..6fd619a0b4 100644 --- a/program-tests/compressed-token-test/tests/mint/functional.rs +++ b/program-tests/compressed-token-test/tests/mint/functional.rs @@ -1208,6 +1208,7 @@ async fn test_mint_actions() { let expected_recipients: Vec = recipients.clone(); // Create empty pre-states since everything was created from scratch + let (_, mint_bump) = find_mint_address(&mint_seed.pubkey()); let empty_pre_compressed_mint = CompressedMint { base: BaseMint { mint_authority: Some(new_mint_authority.pubkey().into()), @@ -1220,9 +1221,10 @@ async fn test_mint_actions() { version: 3, // With metadata mint: spl_mint_pda.into(), cmint_decompressed: false, // Becomes true after DecompressMint action - compressed_address: compressed_mint_address, + mint_signer: mint_seed.pubkey().into(), + bump: mint_bump, }, - reserved: [0u8; 17], + reserved: [0u8; 16], account_type: ACCOUNT_TYPE_MINT, compression: CompressionInfo::default(), extensions: Some(vec![ @@ -1442,6 +1444,7 @@ async fn test_create_compressed_mint_with_cmint() { println!("Create mint + CMint signature: {}", signature); // Build pre-state for DecompressMint assertion (state before DecompressMint was applied) + let (_, cmint_bump) = find_mint_address(&mint_seed.pubkey()); let pre_decompress_mint = CompressedMint { base: BaseMint { mint_authority: Some(mint_authority.pubkey().into()), @@ -1454,9 +1457,10 @@ async fn test_create_compressed_mint_with_cmint() { version: 3, cmint_decompressed: false, // Before DecompressMint mint: cmint_pda.to_bytes().into(), - compressed_address: compressed_mint_address, + mint_signer: mint_seed.pubkey().into(), + bump: cmint_bump, }, - reserved: [0u8; 17], + reserved: [0u8; 16], account_type: ACCOUNT_TYPE_MINT, compression: CompressionInfo::default(), extensions: None, @@ -1468,7 +1472,6 @@ async fn test_create_compressed_mint_with_cmint() { compressed_mint_address, pre_decompress_mint, vec![MintActionType::DecompressMint { - cmint_bump: _cmint_bump, rent_payment: 2, // Default rent payment write_top_up: 0, // Default write top-up }], @@ -1669,7 +1672,7 @@ async fn test_decompress_existing_mint_to_cmint() { let address_tree_pubkey = rpc.get_address_tree_v2().tree; let compressed_mint_address = derive_mint_compressed_address(&mint_seed.pubkey(), &address_tree_pubkey); - let (cmint_pda, cmint_bump) = find_mint_address(&mint_seed.pubkey()); + let (cmint_pda, _cmint_bump) = find_mint_address(&mint_seed.pubkey()); // === STEP 1: Create compressed mint WITHOUT CMint === create_mint( @@ -1759,7 +1762,6 @@ async fn test_decompress_existing_mint_to_cmint() { compressed_mint_address, compressed_mint_after_mint, vec![MintActionType::DecompressMint { - cmint_bump, rent_payment: 2, // Default rent payment write_top_up: 0, // Default write top-up }], diff --git a/program-tests/utils/src/mint_assert.rs b/program-tests/utils/src/mint_assert.rs index 126d787d91..d2e7f2ce2f 100644 --- a/program-tests/utils/src/mint_assert.rs +++ b/program-tests/utils/src/mint_assert.rs @@ -17,6 +17,16 @@ pub fn assert_compressed_mint_account( freeze_authority: Pubkey, metadata: Option, ) -> CompressedMint { + // Derive mint_signer from spl_mint_pda by reversing the PDA derivation + // We need to find the mint_signer and bump used to create spl_mint_pda + // spl_mint_pda = PDA([COMPRESSED_MINT_SEED, mint_signer], program_id) + // Since we can't reverse this, we extract it from the actual compressed mint data + let compressed_account_data = compressed_mint_account.data.clone().unwrap(); + let actual_compressed_mint: CompressedMint = + BorshDeserialize::deserialize(&mut compressed_account_data.data.as_slice()).unwrap(); + let mint_signer = actual_compressed_mint.metadata.mint_signer; + let bump = actual_compressed_mint.metadata.bump; + // Create expected extensions if metadata is provided let expected_extensions = metadata.map(|meta| { vec![ExtensionStruct::TokenMetadata( @@ -46,9 +56,10 @@ pub fn assert_compressed_mint_account( version: 3, mint: spl_mint_pda.into(), cmint_decompressed: false, - compressed_address: compressed_mint_address, + mint_signer, + bump, }, - reserved: [0u8; 17], + reserved: [0u8; 16], account_type: ACCOUNT_TYPE_MINT, compression: light_compressible::compression_info::CompressionInfo::default(), extensions: expected_extensions, diff --git a/programs/compressed-token/anchor/src/lib.rs b/programs/compressed-token/anchor/src/lib.rs index f045a3ccff..e1ef6c680f 100644 --- a/programs/compressed-token/anchor/src/lib.rs +++ b/programs/compressed-token/anchor/src/lib.rs @@ -588,6 +588,8 @@ pub enum ErrorCode { CompressAndCloseCMintMustBeOnlyAction, // 6169 #[msg("Idempotent early exit - not a real error, used to skip CPI")] IdempotentEarlyExit, // 6170 + #[msg("Mint signer mismatch between account and instruction data")] + MintActionInvalidMintSigner, // 6171 } /// Anchor error code offset - error codes start at 6000 diff --git a/programs/compressed-token/program/src/compressed_token/mint_action/accounts.rs b/programs/compressed-token/program/src/compressed_token/mint_action/accounts.rs index c7acefa99b..c9f68a9052 100644 --- a/programs/compressed-token/program/src/compressed_token/mint_action/accounts.rs +++ b/programs/compressed-token/program/src/compressed_token/mint_action/accounts.rs @@ -79,7 +79,7 @@ impl<'info> MintActionAccounts<'info> { let mint_signer = if config.mint_signer_must_sign() { iter.next_option_signer("mint_signer", config.with_mint_signer)? } else { - iter.next_option("mint_signer", config.with_mint_signer)? + None }; // Static non-CPI accounts first // Authority is always required to sign @@ -444,10 +444,9 @@ impl AccountsConfig { return Err(ErrorCode::CompressAndCloseCMintMustBeOnlyAction.into()); } - // We need mint signer if create mint or decompress mint. + // We need mint signer only if creating a new mint. // CompressAndCloseCMint does NOT need mint_signer - it verifies CMint by compressed_mint.metadata.mint - let with_mint_signer = - parsed_instruction_data.create_mint.is_some() || has_decompress_mint_action; + let with_mint_signer = parsed_instruction_data.create_mint.is_some(); // CMint account needed when mint is already decompressed (metadata flag) // When mint is None, CMint is decompressed (data lives in CMint account, compressed account is empty) let cmint_decompressed = parsed_instruction_data.mint.is_none(); diff --git a/programs/compressed-token/program/src/compressed_token/mint_action/actions/create_mint.rs b/programs/compressed-token/program/src/compressed_token/mint_action/actions/create_mint.rs index 7672464c42..b10593744e 100644 --- a/programs/compressed-token/program/src/compressed_token/mint_action/actions/create_mint.rs +++ b/programs/compressed-token/program/src/compressed_token/mint_action/actions/create_mint.rs @@ -26,18 +26,28 @@ pub fn process_create_mint_action( // 1. Derive compressed mint address without bump to ensure // that only one mint per seed can be created. - let mint_pda = solana_pubkey::Pubkey::find_program_address( + let (mint_pda, mint_pda_bump) = solana_pubkey::Pubkey::find_program_address( &[COMPRESSED_MINT_SEED, mint_signer.as_slice()], &crate::ID, - ) - .0 - .to_bytes(); + ); + let mint_pda = mint_pda.to_bytes(); parsed_instruction_data .create_mint .as_ref() .ok_or(ProgramError::InvalidInstructionData)?; + // 1. Validate mint_signer matches account + if mint_signer.as_slice() != mint.metadata.mint_signer.array_ref() { + msg!("Mint signer mismatch"); + return Err(ErrorCode::MintActionInvalidMintSigner.into()); + } + if mint_pda_bump != mint.metadata.bump { + msg!("Mint bump mismatch"); + return Err(ErrorCode::MintActionInvalidMintBump.into()); + } + + // 3. Validate derived PDA matches stored mint if !pubkey_eq(&mint_pda, mint.metadata.mint.array_ref()) { msg!("Invalid mint PDA derivation"); return Err(ErrorCode::MintActionInvalidMintPda.into()); @@ -60,7 +70,9 @@ pub fn process_create_mint_action( &cpi_context.address_tree_pubkey, &crate::LIGHT_CPI_SIGNER.program_id, ); - if address != mint.metadata.compressed_address { + // Validate derived address matches the compressed_address from metadata + // (which derives from mint PDA, CMINT_ADDRESS_TREE, and LIGHT_TOKEN_PROGRAM_ID) + if address != mint.metadata.compressed_address() { msg!("Invalid compressed mint address derivation"); return Err(ErrorCode::MintActionInvalidCompressedMintAddress.into()); } diff --git a/programs/compressed-token/program/src/compressed_token/mint_action/actions/decompress_mint.rs b/programs/compressed-token/program/src/compressed_token/mint_action/actions/decompress_mint.rs index a46f03c26e..288b1c6c43 100644 --- a/programs/compressed-token/program/src/compressed_token/mint_action/actions/decompress_mint.rs +++ b/programs/compressed-token/program/src/compressed_token/mint_action/actions/decompress_mint.rs @@ -43,7 +43,6 @@ pub fn process_decompress_mint_action( action: &ZDecompressMintAction, compressed_mint: &mut CompressedMint, validated_accounts: &MintActionAccounts, - mint_signer: &AccountInfo, fee_payer: &AccountInfo, ) -> Result<(), ProgramError> { // 1. Check not already decompressed @@ -111,12 +110,13 @@ pub fn process_decompress_mint_action( rent_config: config.rent_config, }; - // 6. Verify PDA derivation - let seeds: [&[u8]; 2] = [COMPRESSED_MINT_SEED, mint_signer.key()]; + // 6. Verify PDA derivation using stored mint_signer from compressed_mint metadata + let pda_mint_signer_bytes: &[u8] = compressed_mint.metadata.mint_signer.as_ref(); + let seeds: [&[u8]; 2] = [COMPRESSED_MINT_SEED, pda_mint_signer_bytes]; verify_pda( cmint.key(), &seeds, - action.cmint_bump, + compressed_mint.metadata.bump, &crate::LIGHT_CPI_SIGNER.program_id, )?; // 6b. Verify CMint account matches compressed_mint.metadata.mint @@ -153,11 +153,12 @@ pub fn process_decompress_mint_action( Seed::from(rent_sponsor_bump_bytes.as_ref()), ]; - // 7d. Build seeds for CMint PDA - let cmint_bump_bytes = [action.cmint_bump]; + // 7d. Build seeds for CMint PDA using stored values from compressed_mint metadata + let cmint_bump_bytes = [compressed_mint.metadata.bump]; + let mint_signer_bytes: &[u8] = compressed_mint.metadata.mint_signer.as_ref(); let cmint_seeds = [ Seed::from(COMPRESSED_MINT_SEED), - Seed::from(mint_signer.key()), + Seed::from(mint_signer_bytes), Seed::from(cmint_bump_bytes.as_ref()), ]; diff --git a/programs/compressed-token/program/src/compressed_token/mint_action/actions/process_actions.rs b/programs/compressed-token/program/src/compressed_token/mint_action/actions/process_actions.rs index 0134cf9cc1..10a05ea675 100644 --- a/programs/compressed-token/program/src/compressed_token/mint_action/actions/process_actions.rs +++ b/programs/compressed-token/program/src/compressed_token/mint_action/actions/process_actions.rs @@ -128,9 +128,6 @@ pub fn process_actions<'a>( )?; } ZAction::DecompressMint(decompress_action) => { - let mint_signer = validated_accounts - .mint_signer - .ok_or(ErrorCode::MintActionMissingMintSigner)?; let fee_payer = validated_accounts .executing .as_ref() @@ -143,7 +140,6 @@ pub fn process_actions<'a>( decompress_action, compressed_mint, validated_accounts, - mint_signer, fee_payer, )?; } diff --git a/programs/compressed-token/program/src/compressed_token/mint_action/mint_input.rs b/programs/compressed-token/program/src/compressed_token/mint_action/mint_input.rs index 7d03d9d96d..7b79d81884 100644 --- a/programs/compressed-token/program/src/compressed_token/mint_action/mint_input.rs +++ b/programs/compressed-token/program/src/compressed_token/mint_action/mint_input.rs @@ -48,7 +48,7 @@ pub fn create_input_compressed_mint_account( &merkle_context, root_index, 0, - Some(compressed_mint.metadata.compressed_address.as_ref()), + Some(&compressed_mint.metadata.compressed_address()), )?; Ok(()) diff --git a/programs/compressed-token/program/src/compressed_token/mint_action/mint_output.rs b/programs/compressed-token/program/src/compressed_token/mint_action/mint_output.rs index 0840bad7a1..8d4b17e122 100644 --- a/programs/compressed-token/program/src/compressed_token/mint_action/mint_output.rs +++ b/programs/compressed-token/program/src/compressed_token/mint_action/mint_output.rs @@ -117,7 +117,7 @@ fn serialize_compressed_mint<'a>( mint_account.set( crate::LIGHT_CPI_SIGNER.program_id.into(), 0, - Some(compressed_mint.metadata.compressed_address), + Some(compressed_mint.metadata.compressed_address()), queue_indices.output_queue_index, discriminator, data_hash, diff --git a/programs/compressed-token/program/tests/mint.rs b/programs/compressed-token/program/tests/mint.rs index 5563cf67d0..b23f334e05 100644 --- a/programs/compressed-token/program/tests/mint.rs +++ b/programs/compressed-token/program/tests/mint.rs @@ -112,6 +112,9 @@ fn test_rnd_create_compressed_mint_account() { }; // Step 2: Create CompressedMintInstructionData using current API + // mint_signer is a random pubkey used as seed for the mint PDA + let mint_signer = Pubkey::new_from_array(rng.gen::<[u8; 32]>()); + let bump: u8 = rng.gen(); let mint_instruction_data = CompressedMintInstructionData { supply: input_supply, decimals, @@ -119,7 +122,8 @@ fn test_rnd_create_compressed_mint_account() { version, mint: mint_pda, cmint_decompressed, - compressed_address: compressed_account_address, + mint_signer, + bump, }, mint_authority: Some(mint_authority), freeze_authority, @@ -378,9 +382,10 @@ fn test_compressed_mint_borsh_zero_copy_compatibility() { version: 3u8, mint: Pubkey::new_from_array([3; 32]), cmint_decompressed: false, - compressed_address: [5; 32], + mint_signer: Pubkey::new_from_array([5; 32]), + bump: 255, }, - reserved: [0u8; 17], + reserved: [0u8; 16], account_type: ACCOUNT_TYPE_MINT, extensions: Some(vec![ExtensionStruct::TokenMetadata(token_metadata)]), }; @@ -433,7 +438,8 @@ fn test_compressed_mint_borsh_zero_copy_compatibility() { version: zc_mint.base.metadata.version, mint: zc_mint.base.metadata.mint, cmint_decompressed: zc_mint.base.metadata.cmint_decompressed != 0, - compressed_address: zc_mint.base.metadata.compressed_address, + mint_signer: zc_mint.base.metadata.mint_signer, + bump: zc_mint.base.metadata.bump, }, reserved: *zc_mint.base.reserved, account_type: zc_mint.base.account_type, diff --git a/programs/compressed-token/program/tests/mint_action.rs b/programs/compressed-token/program/tests/mint_action.rs index d8ec62a28b..cadd46a506 100644 --- a/programs/compressed-token/program/tests/mint_action.rs +++ b/programs/compressed-token/program/tests/mint_action.rs @@ -42,7 +42,8 @@ fn random_compressed_mint_metadata(rng: &mut StdRng) -> CompressedMintMetadata { version: rng.gen_range(1..=3) as u8, cmint_decompressed: rng.gen_bool(0.5), mint: random_pubkey(rng), - compressed_address: rng.gen::<[u8; 32]>(), + mint_signer: random_pubkey(rng), + bump: rng.gen(), } } diff --git a/sdk-libs/token-client/src/actions/mint_action.rs b/sdk-libs/token-client/src/actions/mint_action.rs index 654dceaa0d..b13071d0e2 100644 --- a/sdk-libs/token-client/src/actions/mint_action.rs +++ b/sdk-libs/token-client/src/actions/mint_action.rs @@ -23,7 +23,7 @@ use crate::instructions::mint_action::{ /// * `params` - Parameters for the mint action /// * `authority` - Authority keypair for the mint operations /// * `payer` - Account that pays for the transaction -/// * `mint_signer` - Optional mint signer for create_mint or DecompressMint action +/// * `mint_signer` - Optional mint signer for create_mint action only pub async fn mint_action( rpc: &mut R, params: MintActionParams, @@ -133,12 +133,8 @@ pub async fn mint_action_comprehensive( } // Add DecompressMint action if requested - // Check before moving to use later for mint_signer determination - let has_decompress_mint = decompress_mint.is_some(); if let Some(decompress_params) = decompress_mint { - let (_, cmint_bump) = find_mint_address(&mint_seed.pubkey()); actions.push(MintActionType::DecompressMint { - cmint_bump, rent_payment: decompress_params.rent_payment, write_top_up: decompress_params.write_top_up, }); @@ -150,8 +146,9 @@ pub async fn mint_action_comprehensive( } // Determine if mint_signer is needed - matches onchain logic: - // with_mint_signer = create_mint() | has_DecompressMint_action - let mint_signer = if new_mint.is_some() || has_decompress_mint { + // with_mint_signer = create_mint() only + // DecompressMint does NOT need mint_signer - it uses compressed_mint.metadata.mint_signer + let mint_signer = if new_mint.is_some() { Some(mint_seed) } else { None diff --git a/sdk-libs/token-client/src/instructions/mint_action.rs b/sdk-libs/token-client/src/instructions/mint_action.rs index 0a325df35b..41650305d5 100644 --- a/sdk-libs/token-client/src/instructions/mint_action.rs +++ b/sdk-libs/token-client/src/instructions/mint_action.rs @@ -69,7 +69,6 @@ pub enum MintActionType { /// Decompress the compressed mint to a CMint Solana account. /// CMint is always compressible - rent_payment must be >= 2. DecompressMint { - cmint_bump: u8, /// Rent payment in epochs (prepaid). Must be >= 2. rent_payment: u8, /// Lamports allocated for future write operations (top-up per write). @@ -149,16 +148,18 @@ pub async fn create_mint_action_instruction( RpcError::CustomError("NewMint parameters required for mint creation".to_string()) })?; + let (mint_pda, bump) = find_mint_address(¶ms.mint_seed); let mint_data = light_token_interface::instructions::mint_action::CompressedMintInstructionData { supply: new_mint.supply, decimals: new_mint.decimals, metadata: light_token_interface::state::CompressedMintMetadata { version: new_mint.version, - mint: find_mint_address(¶ms.mint_seed).0.to_bytes().into(), + mint: mint_pda.to_bytes().into(), // false for new mint - on-chain sets to true after DecompressMint cmint_decompressed: false, - compressed_address: params.compressed_mint_address, + mint_signer: params.mint_seed.to_bytes().into(), + bump, }, mint_authority: Some(new_mint.mint_authority.to_bytes().into()), freeze_authority: new_mint.freeze_authority.map(|auth| auth.to_bytes().into()), @@ -304,11 +305,9 @@ pub async fn create_mint_action_instruction( idempotent, }), MintActionType::DecompressMint { - cmint_bump, rent_payment, write_top_up, } => instruction_data.with_decompress_mint(DecompressMintAction { - cmint_bump, rent_payment, write_top_up, }), @@ -367,12 +366,8 @@ pub async fn create_mint_action_instruction( config_address, compressible_config.rent_sponsor, ); - // DecompressMint needs mint_signer even when not creating a new mint - // (for PDA derivation of CMint account) + // DecompressMint does NOT need mint_signer - it uses compressed_mint.metadata.mint_signer // CompressAndCloseCMint does NOT need mint_signer - it verifies CMint via compressed_mint.metadata.mint - if has_decompress_mint && !is_creating_mint { - config = config.with_mint_signer(params.mint_seed); - } } // Get account metas @@ -459,9 +454,7 @@ pub async fn create_comprehensive_mint_action_instruction( // Add DecompressMint action if requested if let Some(decompress_params) = decompress_mint { - let (_, cmint_bump) = find_mint_address(&mint_seed.pubkey()); actions.push(MintActionType::DecompressMint { - cmint_bump, rent_payment: decompress_params.rent_payment, write_top_up: decompress_params.write_top_up, }); diff --git a/sdk-libs/token-sdk/src/compressed_token/v2/create_compressed_mint/instruction.rs b/sdk-libs/token-sdk/src/compressed_token/v2/create_compressed_mint/instruction.rs index 70b627bd33..d0fcc413bf 100644 --- a/sdk-libs/token-sdk/src/compressed_token/v2/create_compressed_mint/instruction.rs +++ b/sdk-libs/token-sdk/src/compressed_token/v2/create_compressed_mint/instruction.rs @@ -41,18 +41,20 @@ pub struct CreateCompressedMintInputs { /// Creates a compressed mint instruction with a pre-computed mint address (wrapper around mint_action) pub fn create_compressed_mint_cpi( input: CreateCompressedMintInputs, - mint_address: [u8; 32], + _mint_address: [u8; 32], cpi_context: Option, cpi_context_pubkey: Option, ) -> Result { + let (mint_pda, bump) = find_mint_address(&input.mint_signer); let compressed_mint_instruction_data = CompressedMintInstructionData { supply: 0, decimals: input.decimals, metadata: light_token_interface::state::CompressedMintMetadata { version: input.version, - mint: find_mint_address(&input.mint_signer).0.to_bytes().into(), + mint: mint_pda.to_bytes().into(), cmint_decompressed: false, - compressed_address: mint_address, + mint_signer: input.mint_signer.to_bytes().into(), + bump, }, mint_authority: Some(input.mint_authority.to_bytes().into()), freeze_authority: input.freeze_authority.map(|auth| auth.to_bytes().into()), @@ -127,14 +129,16 @@ pub fn create_compressed_mint_cpi_write( return Err(TokenSdkError::InvalidAccountData); } + let (mint_pda, bump) = find_mint_address(&input.mint_signer); let compressed_mint_instruction_data = CompressedMintInstructionData { supply: 0, decimals: input.decimals, metadata: light_token_interface::state::CompressedMintMetadata { version: input.version, - mint: find_mint_address(&input.mint_signer).0.to_bytes().into(), + mint: mint_pda.to_bytes().into(), cmint_decompressed: false, - compressed_address: input.mint_address, + mint_signer: input.mint_signer.to_bytes().into(), + bump, }, mint_authority: Some(input.mint_authority.to_bytes().into()), freeze_authority: input.freeze_authority.map(|auth| auth.to_bytes().into()), diff --git a/sdk-libs/token-sdk/src/token/create_mint.rs b/sdk-libs/token-sdk/src/token/create_mint.rs index 4e8c685c58..3e51f0daeb 100644 --- a/sdk-libs/token-sdk/src/token/create_mint.rs +++ b/sdk-libs/token-sdk/src/token/create_mint.rs @@ -112,16 +112,17 @@ impl CreateMint { } pub fn instruction(self) -> Result { - let compression_address = self.params.compression_address; + let (mint_pda, bump) = find_mint_address(&self.mint_seed_pubkey); let compressed_mint_instruction_data = CompressedMintInstructionData { supply: 0, decimals: self.params.decimals, metadata: light_token_interface::state::CompressedMintMetadata { version: 3, - mint: self.params.mint.to_bytes().into(), + mint: mint_pda.to_bytes().into(), cmint_decompressed: false, - compressed_address: compression_address, + mint_signer: self.mint_seed_pubkey.to_bytes().into(), + bump, }, mint_authority: Some(self.params.mint_authority.to_bytes().into()), freeze_authority: self @@ -255,14 +256,16 @@ impl CreateCompressedMintCpiWrite { return Err(ProgramError::InvalidAccountData); } + let (mint_pda, bump) = find_mint_address(&self.mint_signer); let compressed_mint_instruction_data = CompressedMintInstructionData { supply: 0, decimals: self.params.decimals, metadata: light_token_interface::state::CompressedMintMetadata { version: self.params.version, - mint: self.params.mint.to_bytes().into(), + mint: mint_pda.to_bytes().into(), cmint_decompressed: false, - compressed_address: self.params.compression_address, + mint_signer: self.mint_signer.to_bytes().into(), + bump, }, mint_authority: Some(self.params.mint_authority.to_bytes().into()), freeze_authority: self diff --git a/sdk-libs/token-sdk/src/token/decompress_mint.rs b/sdk-libs/token-sdk/src/token/decompress_mint.rs index ec10a7e523..c08b2abbe1 100644 --- a/sdk-libs/token-sdk/src/token/decompress_mint.rs +++ b/sdk-libs/token-sdk/src/token/decompress_mint.rs @@ -61,11 +61,10 @@ pub struct DecompressMint { impl DecompressMint { pub fn instruction(self) -> Result { // Derive CMint PDA - let (cmint_pda, cmint_bump) = find_mint_address(&self.mint_seed_pubkey); + let (cmint_pda, _cmint_bump) = find_mint_address(&self.mint_seed_pubkey); // Build DecompressMintAction let action = DecompressMintAction { - cmint_bump, rent_payment: self.rent_payment, write_top_up: self.write_top_up, }; @@ -78,6 +77,7 @@ impl DecompressMint { .with_decompress_mint(action); // Build account metas with compressible CMint + // Note: mint_signer is NOT needed for decompress_mint - it uses compressed_mint.metadata.mint_signer let meta_config = MintActionMetaConfig::new( self.payer, self.authority, @@ -85,8 +85,7 @@ impl DecompressMint { self.input_queue, self.output_queue, ) - .with_compressible_mint(cmint_pda, config_pda(), rent_sponsor_pda()) - .with_mint_signer_no_sign(self.mint_seed_pubkey); + .with_compressible_mint(cmint_pda, config_pda(), rent_sponsor_pda()); let account_metas = meta_config.to_account_metas(); diff --git a/sdk-tests/csdk-anchor-derived-test/tests/basic_test.rs b/sdk-tests/csdk-anchor-derived-test/tests/basic_test.rs index 7a466209ca..38b4f36e78 100644 --- a/sdk-tests/csdk-anchor-derived-test/tests/basic_test.rs +++ b/sdk-tests/csdk-anchor-derived-test/tests/basic_test.rs @@ -671,7 +671,8 @@ pub async fn create_user_record_and_game_session( version: 3, mint: spl_mint.into(), cmint_decompressed: false, - compressed_address: compressed_mint_address, + mint_signer: mint_signer.pubkey().into(), + bump: mint_bump, }, mint_authority: Some(mint_authority.into()), freeze_authority: Some(freeze_authority.into()), diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/basic_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/basic_test.rs index 55504f129f..6112fa0e70 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/basic_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/basic_test.rs @@ -331,7 +331,8 @@ pub async fn create_user_record_and_game_session( version: 3, mint: spl_mint.into(), cmint_decompressed: false, - compressed_address: compressed_mint_address, + mint_signer: mint_signer.pubkey().into(), + bump: mint_bump, }, mint_authority: Some(mint_authority.into()), freeze_authority: Some(freeze_authority.into()), diff --git a/sdk-tests/sdk-compressible-test/tests/multi_account_tests.rs b/sdk-tests/sdk-compressible-test/tests/multi_account_tests.rs index 7b41ff0e22..dff02e01ae 100644 --- a/sdk-tests/sdk-compressible-test/tests/multi_account_tests.rs +++ b/sdk-tests/sdk-compressible-test/tests/multi_account_tests.rs @@ -332,7 +332,8 @@ pub async fn create_user_record_and_game_session( version: 3, mint: spl_mint.into(), cmint_decompressed: false, - compressed_address: compressed_mint_address, + mint_signer: mint_signer.pubkey().into(), + bump: mint_bump, }, mint_authority: Some(mint_authority.into()), freeze_authority: Some(freeze_authority.into()), diff --git a/sdk-tests/sdk-token-test/Cargo.toml b/sdk-tests/sdk-token-test/Cargo.toml index c866c55d14..9871497514 100644 --- a/sdk-tests/sdk-token-test/Cargo.toml +++ b/sdk-tests/sdk-token-test/Cargo.toml @@ -56,6 +56,7 @@ light-compressed-account = { workspace = true, features = ["anchor"] } light-client = { workspace = true, features = ["devenv"] } light-token-client = { workspace = true } light-compressible = { workspace = true } +borsh = { workspace = true } [lints.rust.unexpected_cfgs] level = "allow" diff --git a/sdk-tests/sdk-token-test/tests/ctoken_pda.rs b/sdk-tests/sdk-token-test/tests/ctoken_pda.rs index 24af72d9bf..56bf3bd5f5 100644 --- a/sdk-tests/sdk-token-test/tests/ctoken_pda.rs +++ b/sdk-tests/sdk-token-test/tests/ctoken_pda.rs @@ -149,7 +149,7 @@ pub async fn create_mint( derive_mint_compressed_address(&mint_seed.pubkey(), &address_tree_pubkey); // Find mint bump for the instruction - let (mint, _) = find_mint_address(&mint_seed.pubkey()); + let (mint, mint_bump) = find_mint_address(&mint_seed.pubkey()); let pda_address_seed = hash_to_bn254_field_size_be( [b"escrow", payer.pubkey().to_bytes().as_ref()] @@ -202,7 +202,8 @@ pub async fn create_mint( version: 3, mint: mint.into(), cmint_decompressed: false, - compressed_address: compressed_mint_address, + mint_signer: mint_seed.pubkey().into(), + bump: mint_bump, }, mint_authority: Some(mint_authority.pubkey().into()), freeze_authority: freeze_authority.map(|fa| fa.into()), diff --git a/sdk-tests/sdk-token-test/tests/pda_ctoken.rs b/sdk-tests/sdk-token-test/tests/pda_ctoken.rs index 9772ebd75e..5ab4ed3855 100644 --- a/sdk-tests/sdk-token-test/tests/pda_ctoken.rs +++ b/sdk-tests/sdk-token-test/tests/pda_ctoken.rs @@ -195,7 +195,7 @@ pub async fn create_mint( derive_mint_compressed_address(&mint_seed.pubkey(), &address_tree_pubkey); // Find mint bump for the instruction - let (mint, _) = find_mint_address(&mint_seed.pubkey()); + let (mint, mint_bump) = find_mint_address(&mint_seed.pubkey()); // Create compressed token associated token account for the mint authority let (token_account, _) = derive_token_ata(&mint_authority.pubkey(), &mint); @@ -273,7 +273,8 @@ pub async fn create_mint( version: 3, mint: mint.into(), cmint_decompressed: false, - compressed_address: compressed_mint_address, + mint_signer: mint_seed.pubkey().into(), + bump: mint_bump, }, mint_authority: Some(mint_authority.pubkey().into()), freeze_authority: freeze_authority.map(|fa| fa.into()), diff --git a/sdk-tests/sdk-token-test/tests/test_4_transfer2.rs b/sdk-tests/sdk-token-test/tests/test_4_transfer2.rs index aad7110f3e..c0970dfe4d 100644 --- a/sdk-tests/sdk-token-test/tests/test_4_transfer2.rs +++ b/sdk-tests/sdk-token-test/tests/test_4_transfer2.rs @@ -157,7 +157,7 @@ async fn create_compressed_mint_helper( // Find mint PDA let compressed_token_program_id = Pubkey::new_from_array(light_token_interface::LIGHT_TOKEN_PROGRAM_ID); - let (mint_pda, _) = Pubkey::find_program_address( + let (mint_pda, _mint_bump) = Pubkey::find_program_address( &[COMPRESSED_MINT_SEED, mint_signer.pubkey().as_ref()], &compressed_token_program_id, ); @@ -228,7 +228,13 @@ async fn mint_compressed_tokens( .ok_or("Compressed mint account not found") .unwrap(); - // Create expected compressed mint for the input + // Extract mint_signer and bump from the actual compressed mint account + use borsh::BorshDeserialize; + let compressed_account_data = compressed_mint_account.data.clone().unwrap(); + let actual_compressed_mint: light_token_interface::state::CompressedMint = + BorshDeserialize::deserialize(&mut compressed_account_data.data.as_slice()).unwrap(); + + // Create expected compressed mint for the input using actual mint_signer and bump let expected_compressed_mint = light_token_interface::state::CompressedMint { base: BaseMint { mint_authority: Some(payer.pubkey().into()), @@ -241,9 +247,10 @@ async fn mint_compressed_tokens( version: 3, mint: mint_pda.into(), cmint_decompressed: false, - compressed_address: compressed_mint_account.address.unwrap(), + mint_signer: actual_compressed_mint.metadata.mint_signer, + bump: actual_compressed_mint.metadata.bump, }, - reserved: [0u8; 17], + reserved: [0u8; 16], account_type: ACCOUNT_TYPE_MINT, compression: Default::default(), extensions: None, diff --git a/sdk-tests/sdk-token-test/tests/test_compress_full_and_close.rs b/sdk-tests/sdk-token-test/tests/test_compress_full_and_close.rs index 96e08b9395..6addb72e1c 100644 --- a/sdk-tests/sdk-token-test/tests/test_compress_full_and_close.rs +++ b/sdk-tests/sdk-token-test/tests/test_compress_full_and_close.rs @@ -53,7 +53,7 @@ async fn test_compress_full_and_close() { let compressed_token_program_id = Pubkey::new_from_array(light_token_interface::LIGHT_TOKEN_PROGRAM_ID); - let (mint_pda, _) = Pubkey::find_program_address( + let (mint_pda, mint_bump) = Pubkey::find_program_address( &[COMPRESSED_MINT_SEED, mint_signer.pubkey().as_ref()], &compressed_token_program_id, ); @@ -132,9 +132,10 @@ async fn test_compress_full_and_close() { version: 3, mint: mint_pda.into(), cmint_decompressed: false, - compressed_address: compressed_mint_address, + mint_signer: mint_signer.pubkey().into(), + bump: mint_bump, }, - reserved: [0u8; 17], + reserved: [0u8; 16], account_type: ACCOUNT_TYPE_MINT, compression: Default::default(), extensions: None, From f56835ff7d07995022a7bb698621decfad8ff8b5 Mon Sep 17 00:00:00 2001 From: ananas Date: Thu, 15 Jan 2026 20:50:22 +0000 Subject: [PATCH 2/3] stash doc fixes --- .../src/state/mint/compressed_mint.rs | 2 +- .../token-interface/tests/compressed_mint.rs | 8 +- .../tests/cross_deserialization.rs | 2 +- .../tests/mint_borsh_zero_copy.rs | 12 +- .../tests/mint/cpi_context.rs | 4 +- .../tests/mint/functional.rs | 4 +- .../docs/compressed_token/MINT_ACTION.md | 4 +- .../compressed_token/mint_action/accounts.rs | 6 +- .../mint_action/actions/create_mint.rs | 12 +- .../compressed_token/mint_action/processor.rs | 2 +- .../compressed-token/program/tests/mint.rs | 4 +- .../program/tests/mint_action.rs | 2 +- .../src/instructions/create_mint.rs | 5 +- .../src/instructions/mint_action.rs | 2 +- .../v2/create_compressed_mint/instruction.rs | 11 +- .../v2/mint_action/account_metas.rs | 8 - sdk-libs/token-sdk/src/token/create_mint.rs | 210 +----------------- .../token-sdk/src/token/decompress_mint.rs | 18 +- .../tests/basic_test.rs | 2 +- .../tests/basic_test.rs | 2 +- .../tests/multi_account_tests.rs | 2 +- .../sdk-light-token-test/src/create_cmint.rs | 6 +- .../src/decompress_cmint.rs | 31 ++- .../sdk-light-token-test/tests/shared.rs | 12 +- .../tests/test_create_cmint.rs | 6 +- .../tests/test_ctoken_mint_to.rs | 3 +- .../tests/test_decompress_cmint.rs | 18 +- sdk-tests/sdk-token-test/tests/ctoken_pda.rs | 2 +- sdk-tests/sdk-token-test/tests/pda_ctoken.rs | 2 +- .../tests/test_compress_full_and_close.rs | 2 +- 30 files changed, 99 insertions(+), 305 deletions(-) diff --git a/program-libs/token-interface/src/state/mint/compressed_mint.rs b/program-libs/token-interface/src/state/mint/compressed_mint.rs index e9df255c6f..f4434dd6f0 100644 --- a/program-libs/token-interface/src/state/mint/compressed_mint.rs +++ b/program-libs/token-interface/src/state/mint/compressed_mint.rs @@ -76,7 +76,7 @@ pub struct CompressedMintMetadata { /// Pda with seed address of compressed mint pub mint: Pubkey, /// Signer pubkey used to derive the compressed address - pub mint_signer: Pubkey, + pub mint_signer: [u8; 32], /// Bump seed used for compressed address derivation pub bump: u8, } diff --git a/program-libs/token-interface/tests/compressed_mint.rs b/program-libs/token-interface/tests/compressed_mint.rs index d85bf0d0a6..5d49264753 100644 --- a/program-libs/token-interface/tests/compressed_mint.rs +++ b/program-libs/token-interface/tests/compressed_mint.rs @@ -78,7 +78,7 @@ fn generate_random_compressed_mint(rng: &mut impl Rng, with_extensions: bool) -> version: 3, mint, cmint_decompressed: rng.gen_bool(0.5), - mint_signer: Pubkey::from(rng.gen::<[u8; 32]>()), + mint_signer: rng.gen::<[u8; 32]>(), bump: rng.gen(), }, reserved: [0u8; 16], @@ -262,7 +262,7 @@ fn test_compressed_mint_edge_cases() { version: 3, mint: Pubkey::from([0xff; 32]), cmint_decompressed: false, - mint_signer: Pubkey::from([0u8; 32]), + mint_signer: [0u8; 32], bump: 0, }, reserved: [0u8; 16], @@ -343,7 +343,7 @@ fn test_compressed_mint_edge_cases() { version: 255, mint: Pubkey::from([0xbb; 32]), cmint_decompressed: true, - mint_signer: Pubkey::from([0xcc; 32]), + mint_signer: [0xcc; 32], bump: 255, }, reserved: [0u8; 16], @@ -372,7 +372,7 @@ fn test_base_mint_in_compressed_mint_spl_format() { version: 3, mint: Pubkey::from([3; 32]), cmint_decompressed: false, - mint_signer: Pubkey::from([4u8; 32]), + mint_signer: [4u8; 32], bump: 255, }, reserved: [0u8; 16], diff --git a/program-libs/token-interface/tests/cross_deserialization.rs b/program-libs/token-interface/tests/cross_deserialization.rs index c454e050df..e572fdffd9 100644 --- a/program-libs/token-interface/tests/cross_deserialization.rs +++ b/program-libs/token-interface/tests/cross_deserialization.rs @@ -29,7 +29,7 @@ fn create_test_cmint() -> CompressedMint { version: 3, mint: Pubkey::new_from_array([2; 32]), cmint_decompressed: false, - mint_signer: Pubkey::new_from_array([5u8; 32]), + mint_signer: [5u8; 32], bump: 255, }, reserved: [0u8; 16], diff --git a/program-libs/token-interface/tests/mint_borsh_zero_copy.rs b/program-libs/token-interface/tests/mint_borsh_zero_copy.rs index 8551d1d1a3..111cce75ad 100644 --- a/program-libs/token-interface/tests/mint_borsh_zero_copy.rs +++ b/program-libs/token-interface/tests/mint_borsh_zero_copy.rs @@ -103,7 +103,7 @@ fn generate_random_mint() -> CompressedMint { rng.fill(&mut bytes); Pubkey::from(bytes) }, - mint_signer: Pubkey::from(rng.gen::<[u8; 32]>()), + mint_signer: rng.gen::<[u8; 32]>(), bump: rng.gen(), }, reserved: [0u8; 16], @@ -258,7 +258,7 @@ fn generate_mint_with_extensions() -> CompressedMint { version: 3, cmint_decompressed: rng.gen_bool(0.5), mint: Pubkey::from(rng.gen::<[u8; 32]>()), - mint_signer: Pubkey::from(rng.gen::<[u8; 32]>()), + mint_signer: rng.gen::<[u8; 32]>(), bump: rng.gen(), }, reserved: [0u8; 16], @@ -294,7 +294,7 @@ fn test_mint_extension_edge_cases() { version: 3, cmint_decompressed: false, mint: Pubkey::from([2u8; 32]), - mint_signer: Pubkey::from([0u8; 32]), + mint_signer: [0u8; 32], bump: 0, }, reserved: [0u8; 16], @@ -325,7 +325,7 @@ fn test_mint_extension_edge_cases() { version: 3, cmint_decompressed: true, mint: Pubkey::from([0xbbu8; 32]), - mint_signer: Pubkey::from([0xddu8; 32]), + mint_signer: [0xddu8; 32], bump: 255, }, reserved: [0u8; 16], @@ -369,7 +369,7 @@ fn test_mint_extension_edge_cases() { version: 3, cmint_decompressed: false, mint: Pubkey::from([4u8; 32]), - mint_signer: Pubkey::from([5u8; 32]), + mint_signer: [5u8; 32], bump: 255, }, reserved: [0u8; 16], @@ -400,7 +400,7 @@ fn test_mint_extension_edge_cases() { version: 3, cmint_decompressed: true, mint: Pubkey::from([7u8; 32]), - mint_signer: Pubkey::from([8u8; 32]), + mint_signer: [8u8; 32], bump: 255, }, reserved: [0u8; 16], diff --git a/program-tests/compressed-token-test/tests/mint/cpi_context.rs b/program-tests/compressed-token-test/tests/mint/cpi_context.rs index 7949cee5ce..7293c34c0a 100644 --- a/program-tests/compressed-token-test/tests/mint/cpi_context.rs +++ b/program-tests/compressed-token-test/tests/mint/cpi_context.rs @@ -84,7 +84,7 @@ async fn test_setup() -> TestSetup { version: 3, cmint_decompressed: false, mint: spl_mint_pda.into(), - mint_signer: mint_seed.pubkey().into(), + mint_signer: mint_seed.pubkey().to_bytes(), bump, }, mint_authority: Some(mint_authority.pubkey().into()), @@ -334,7 +334,7 @@ async fn test_write_to_cpi_context_invalid_compressed_address() { // Swap the mint_signer to an invalid one (this should fail validation) // The compressed address will be derived from the invalid mint_signer - let invalid_mint_signer = light_compressed_account::Pubkey::new_from_array([42u8; 32]); + let invalid_mint_signer = [42u8; 32]; // Build instruction data with invalid mint_signer in metadata let mut invalid_mint = compressed_mint_inputs.mint.clone().unwrap(); diff --git a/program-tests/compressed-token-test/tests/mint/functional.rs b/program-tests/compressed-token-test/tests/mint/functional.rs index 6fd619a0b4..bc9c129f28 100644 --- a/program-tests/compressed-token-test/tests/mint/functional.rs +++ b/program-tests/compressed-token-test/tests/mint/functional.rs @@ -1221,7 +1221,7 @@ async fn test_mint_actions() { version: 3, // With metadata mint: spl_mint_pda.into(), cmint_decompressed: false, // Becomes true after DecompressMint action - mint_signer: mint_seed.pubkey().into(), + mint_signer: mint_seed.pubkey().to_bytes(), bump: mint_bump, }, reserved: [0u8; 16], @@ -1457,7 +1457,7 @@ async fn test_create_compressed_mint_with_cmint() { version: 3, cmint_decompressed: false, // Before DecompressMint mint: cmint_pda.to_bytes().into(), - mint_signer: mint_seed.pubkey().into(), + mint_signer: mint_seed.pubkey().to_bytes(), bump: cmint_bump, }, reserved: [0u8; 16], diff --git a/programs/compressed-token/program/docs/compressed_token/MINT_ACTION.md b/programs/compressed-token/program/docs/compressed_token/MINT_ACTION.md index c47869178d..2066a8d75c 100644 --- a/programs/compressed-token/program/docs/compressed_token/MINT_ACTION.md +++ b/programs/compressed-token/program/docs/compressed_token/MINT_ACTION.md @@ -70,8 +70,8 @@ The account ordering differs based on whether writing to CPI context or executin - Light Protocol system program for CPI to create or update the compressed mint account. 2. mint_signer (optional) - - (signer if create_mint is Some, non-signer for DecompressMint) - - Required if create_mint is Some or DecompressMint action is present + - (signer if create_mint is Some) + - Required only if create_mint is Some - PDA seed derivation from compressed mint randomness 3. authority diff --git a/programs/compressed-token/program/src/compressed_token/mint_action/accounts.rs b/programs/compressed-token/program/src/compressed_token/mint_action/accounts.rs index c9f68a9052..e0ad97d6d8 100644 --- a/programs/compressed-token/program/src/compressed_token/mint_action/accounts.rs +++ b/programs/compressed-token/program/src/compressed_token/mint_action/accounts.rs @@ -19,12 +19,12 @@ use crate::shared::{ pub struct MintActionAccounts<'info> { pub light_system_program: &'info AccountInfo, /// Seed for mint PDA derivation. - /// Required for compressed mint creation and DecompressMint. + /// Required only for compressed mint creation. /// Note: mint_signer is not in executing accounts since create mint /// is allowed in combination with write to cpi context. pub mint_signer: Option<&'info AccountInfo>, pub authority: &'info AccountInfo, - /// Reqired accounts to execute an instruction + /// Required accounts to execute an instruction /// with or without cpi context. /// - write_to_cpi_context_system is None pub executing: Option>, @@ -350,7 +350,7 @@ pub struct AccountsConfig { pub cmint_decompressed: bool, /// 5. Mint pub require_token_output_queue: bool, - /// 6. Compressed mint is created or DecompressMint action is present. + /// 6. Compressed mint is created. pub with_mint_signer: bool, /// 7. Compressed mint is created. pub create_mint: bool, diff --git a/programs/compressed-token/program/src/compressed_token/mint_action/actions/create_mint.rs b/programs/compressed-token/program/src/compressed_token/mint_action/actions/create_mint.rs index b10593744e..aaff1bf925 100644 --- a/programs/compressed-token/program/src/compressed_token/mint_action/actions/create_mint.rs +++ b/programs/compressed-token/program/src/compressed_token/mint_action/actions/create_mint.rs @@ -38,12 +38,13 @@ pub fn process_create_mint_action( .ok_or(ProgramError::InvalidInstructionData)?; // 1. Validate mint_signer matches account - if mint_signer.as_slice() != mint.metadata.mint_signer.array_ref() { + if mint_signer.as_slice() != mint.metadata.mint_signer.as_ref() { msg!("Mint signer mismatch"); return Err(ErrorCode::MintActionInvalidMintSigner.into()); } + // 2. Validate bump matches derived bump if mint_pda_bump != mint.metadata.bump { - msg!("Mint bump mismatch"); + msg!("Invalid mint bump"); return Err(ErrorCode::MintActionInvalidMintBump.into()); } @@ -59,7 +60,8 @@ pub fn process_create_mint_action( // -> Therefore we manually verify the compressed address derivation here. // // else is not required since for new_address_params_assigned - // the light system program checks correct address derivation and we check the + // the light system program checks correct address derivation and we check + // the address tree in new_address_params. if let Some(cpi_context) = &parsed_instruction_data.cpi_context { if !pubkey_eq(&cpi_context.address_tree_pubkey, &CMINT_ADDRESS_TREE) { msg!("Invalid address tree pubkey in cpi context"); @@ -70,8 +72,8 @@ pub fn process_create_mint_action( &cpi_context.address_tree_pubkey, &crate::LIGHT_CPI_SIGNER.program_id, ); - // Validate derived address matches the compressed_address from metadata - // (which derives from mint PDA, CMINT_ADDRESS_TREE, and LIGHT_TOKEN_PROGRAM_ID) + // Validate derived address matches the compressed_address computed from metadata + // (derived from mint PDA, CMINT_ADDRESS_TREE, and LIGHT_TOKEN_PROGRAM_ID) if address != mint.metadata.compressed_address() { msg!("Invalid compressed mint address derivation"); return Err(ErrorCode::MintActionInvalidCompressedMintAddress.into()); diff --git a/programs/compressed-token/program/src/compressed_token/mint_action/processor.rs b/programs/compressed-token/program/src/compressed_token/mint_action/processor.rs index eb0ec33f5f..fe79915982 100644 --- a/programs/compressed-token/program/src/compressed_token/mint_action/processor.rs +++ b/programs/compressed-token/program/src/compressed_token/mint_action/processor.rs @@ -130,7 +130,7 @@ pub fn process_mint_action( &accounts_config, &mint, )?; - }; + } let result = process_output_compressed_account( &parsed_instruction_data, diff --git a/programs/compressed-token/program/tests/mint.rs b/programs/compressed-token/program/tests/mint.rs index b23f334e05..6525fdd89a 100644 --- a/programs/compressed-token/program/tests/mint.rs +++ b/programs/compressed-token/program/tests/mint.rs @@ -122,7 +122,7 @@ fn test_rnd_create_compressed_mint_account() { version, mint: mint_pda, cmint_decompressed, - mint_signer, + mint_signer: mint_signer.to_bytes(), bump, }, mint_authority: Some(mint_authority), @@ -382,7 +382,7 @@ fn test_compressed_mint_borsh_zero_copy_compatibility() { version: 3u8, mint: Pubkey::new_from_array([3; 32]), cmint_decompressed: false, - mint_signer: Pubkey::new_from_array([5; 32]), + mint_signer: [5; 32], bump: 255, }, reserved: [0u8; 16], diff --git a/programs/compressed-token/program/tests/mint_action.rs b/programs/compressed-token/program/tests/mint_action.rs index cadd46a506..1f62f31645 100644 --- a/programs/compressed-token/program/tests/mint_action.rs +++ b/programs/compressed-token/program/tests/mint_action.rs @@ -42,7 +42,7 @@ fn random_compressed_mint_metadata(rng: &mut StdRng) -> CompressedMintMetadata { version: rng.gen_range(1..=3) as u8, cmint_decompressed: rng.gen_bool(0.5), mint: random_pubkey(rng), - mint_signer: random_pubkey(rng), + mint_signer: rng.gen::<[u8; 32]>(), bump: rng.gen(), } } diff --git a/sdk-libs/token-client/src/instructions/create_mint.rs b/sdk-libs/token-client/src/instructions/create_mint.rs index e2fee436e5..3993e4d02c 100644 --- a/sdk-libs/token-client/src/instructions/create_mint.rs +++ b/sdk-libs/token-client/src/instructions/create_mint.rs @@ -60,7 +60,7 @@ pub async fn create_compressed_mint_instruction( .value; let address_merkle_tree_root_index = rpc_result.addresses[0].root_index; - + let (mint, bump) = find_mint_address(&mint_seed.pubkey()); // Build params struct manually let params = CreateMintParams { decimals, @@ -68,7 +68,8 @@ pub async fn create_compressed_mint_instruction( mint_authority, proof: rpc_result.proof.0.unwrap(), compression_address: compressed_mint_address, - mint: find_mint_address(&mint_seed.pubkey()).0, + mint, + bump, freeze_authority, extensions, }; diff --git a/sdk-libs/token-client/src/instructions/mint_action.rs b/sdk-libs/token-client/src/instructions/mint_action.rs index 41650305d5..606c18caac 100644 --- a/sdk-libs/token-client/src/instructions/mint_action.rs +++ b/sdk-libs/token-client/src/instructions/mint_action.rs @@ -158,7 +158,7 @@ pub async fn create_mint_action_instruction( mint: mint_pda.to_bytes().into(), // false for new mint - on-chain sets to true after DecompressMint cmint_decompressed: false, - mint_signer: params.mint_seed.to_bytes().into(), + mint_signer: params.mint_seed.to_bytes(), bump, }, mint_authority: Some(new_mint.mint_authority.to_bytes().into()), diff --git a/sdk-libs/token-sdk/src/compressed_token/v2/create_compressed_mint/instruction.rs b/sdk-libs/token-sdk/src/compressed_token/v2/create_compressed_mint/instruction.rs index d0fcc413bf..c497345534 100644 --- a/sdk-libs/token-sdk/src/compressed_token/v2/create_compressed_mint/instruction.rs +++ b/sdk-libs/token-sdk/src/compressed_token/v2/create_compressed_mint/instruction.rs @@ -38,10 +38,9 @@ pub struct CreateCompressedMintInputs { pub version: u8, } -/// Creates a compressed mint instruction with a pre-computed mint address (wrapper around mint_action) +/// Creates a compressed mint instruction (wrapper around mint_action) pub fn create_compressed_mint_cpi( input: CreateCompressedMintInputs, - _mint_address: [u8; 32], cpi_context: Option, cpi_context_pubkey: Option, ) -> Result { @@ -53,7 +52,7 @@ pub fn create_compressed_mint_cpi( version: input.version, mint: mint_pda.to_bytes().into(), cmint_decompressed: false, - mint_signer: input.mint_signer.to_bytes().into(), + mint_signer: input.mint_signer.to_bytes(), bump, }, mint_authority: Some(input.mint_authority.to_bytes().into()), @@ -137,7 +136,7 @@ pub fn create_compressed_mint_cpi_write( version: input.version, mint: mint_pda.to_bytes().into(), cmint_decompressed: false, - mint_signer: input.mint_signer.to_bytes().into(), + mint_signer: input.mint_signer.to_bytes(), bump, }, mint_authority: Some(input.mint_authority.to_bytes().into()), @@ -175,9 +174,7 @@ pub fn create_compressed_mint_cpi_write( /// Creates a compressed mint instruction with automatic mint address derivation pub fn create_compressed_mint(input: CreateCompressedMintInputs) -> Result { - let mint_address = - derive_mint_compressed_address(&input.mint_signer, &input.address_tree_pubkey); - create_compressed_mint_cpi(input, mint_address, None, None) + create_compressed_mint_cpi(input, None, None) } /// Derives the compressed mint address from the mint seed and address tree diff --git a/sdk-libs/token-sdk/src/compressed_token/v2/mint_action/account_metas.rs b/sdk-libs/token-sdk/src/compressed_token/v2/mint_action/account_metas.rs index c1e06ac351..9d900b5837 100644 --- a/sdk-libs/token-sdk/src/compressed_token/v2/mint_action/account_metas.rs +++ b/sdk-libs/token-sdk/src/compressed_token/v2/mint_action/account_metas.rs @@ -123,14 +123,6 @@ impl MintActionMetaConfig { self } - /// Set the mint_signer account without requiring signature. - /// Use for decompress_mint where only PDA derivation is needed. - pub fn with_mint_signer_no_sign(mut self, mint_signer: Pubkey) -> Self { - self.mint_signer = Some(mint_signer); - self.mint_signer_must_sign = false; - self - } - /// Configure compressible CMint with config and rent sponsor. /// CMint is always compressible - this sets all required accounts. pub fn with_compressible_mint( diff --git a/sdk-libs/token-sdk/src/token/create_mint.rs b/sdk-libs/token-sdk/src/token/create_mint.rs index 3e51f0daeb..3e19525ad9 100644 --- a/sdk-libs/token-sdk/src/token/create_mint.rs +++ b/sdk-libs/token-sdk/src/token/create_mint.rs @@ -14,13 +14,7 @@ use solana_instruction::Instruction; use solana_program_error::ProgramError; use solana_pubkey::Pubkey; -use crate::{ - compressed_token::mint_action::{ - get_mint_action_instruction_account_metas_cpi_write, MintActionMetaConfig, - MintActionMetaConfigCpiWrite, - }, - token::SystemAccountInfos, -}; +use crate::{compressed_token::mint_action::MintActionMetaConfig, token::SystemAccountInfos}; // TODO: modify so that it creates a decompressed mint, if you want a compressed mint use light_token_sdk::compressed_token::create_cmint /// Parameters for creating a compressed mint. #[derive(Debug, Clone)] @@ -31,6 +25,7 @@ pub struct CreateMintParams { pub proof: CompressedProof, pub compression_address: [u8; 32], pub mint: Pubkey, + pub bump: u8, pub freeze_authority: Option, pub extensions: Option>, } @@ -112,17 +107,15 @@ impl CreateMint { } pub fn instruction(self) -> Result { - let (mint_pda, bump) = find_mint_address(&self.mint_seed_pubkey); - let compressed_mint_instruction_data = CompressedMintInstructionData { supply: 0, decimals: self.params.decimals, metadata: light_token_interface::state::CompressedMintMetadata { version: 3, - mint: mint_pda.to_bytes().into(), + mint: self.params.mint.to_bytes().into(), cmint_decompressed: false, - mint_signer: self.mint_seed_pubkey.to_bytes().into(), - bump, + mint_signer: self.mint_seed_pubkey.to_bytes(), + bump: self.params.bump, }, mint_authority: Some(self.params.mint_authority.to_bytes().into()), freeze_authority: self @@ -168,141 +161,6 @@ impl CreateMint { } } -// ============================================================================ -// Params Struct: CreateMintCpiWriteParams -// ============================================================================ - -#[derive(Debug, Clone)] -pub struct CreateMintCpiWriteParams { - pub decimals: u8, - pub mint_authority: Pubkey, - pub freeze_authority: Option, - pub address_merkle_tree_root_index: u16, - pub compression_address: [u8; 32], - pub mint: Pubkey, - pub cpi_context: CpiContext, - pub extensions: Option>, - pub version: u8, -} - -impl CreateMintCpiWriteParams { - pub fn new( - decimals: u8, - address_merkle_tree_root_index: u16, - mint_authority: Pubkey, - compression_address: [u8; 32], - mint: Pubkey, - cpi_context: CpiContext, - ) -> Self { - Self { - decimals, - version: 3, - address_merkle_tree_root_index, - mint_authority, - compression_address, - mint, - cpi_context, - freeze_authority: None, - extensions: None, - } - } - - pub fn with_freeze_authority(mut self, freeze_authority: Pubkey) -> Self { - self.freeze_authority = Some(freeze_authority); - self - } - - pub fn with_extensions(mut self, extensions: Vec) -> Self { - self.extensions = Some(extensions); - self - } -} - -// ============================================================================ -// Builder Struct: CreateCompressedMintCpiWrite -// ============================================================================ - -#[derive(Debug, Clone)] -pub struct CreateCompressedMintCpiWrite { - pub mint_signer: Pubkey, - pub payer: Pubkey, - pub cpi_context_pubkey: Pubkey, - pub params: CreateMintCpiWriteParams, -} - -impl CreateCompressedMintCpiWrite { - pub fn new( - mint_signer: Pubkey, - payer: Pubkey, - cpi_context_pubkey: Pubkey, - params: CreateMintCpiWriteParams, - ) -> Self { - Self { - mint_signer, - payer, - cpi_context_pubkey, - params, - } - } - - pub fn instruction(self) -> Result { - let has_valid_context = - self.params.cpi_context.first_set_context || self.params.cpi_context.set_context; - if !has_valid_context { - solana_msg::msg!( - "CPI context invalid: neither first_set_context nor set_context is set: {:?}", - self.params.cpi_context - ); - return Err(ProgramError::InvalidAccountData); - } - - let (mint_pda, bump) = find_mint_address(&self.mint_signer); - let compressed_mint_instruction_data = CompressedMintInstructionData { - supply: 0, - decimals: self.params.decimals, - metadata: light_token_interface::state::CompressedMintMetadata { - version: self.params.version, - mint: mint_pda.to_bytes().into(), - cmint_decompressed: false, - mint_signer: self.mint_signer.to_bytes().into(), - bump, - }, - mint_authority: Some(self.params.mint_authority.to_bytes().into()), - freeze_authority: self - .params - .freeze_authority - .map(|auth| auth.to_bytes().into()), - extensions: self.params.extensions, - }; - - let instruction_data = - light_token_interface::instructions::mint_action::MintActionCompressedInstructionData::new_mint_write_to_cpi_context( - self.params.address_merkle_tree_root_index, - compressed_mint_instruction_data, - self.params.cpi_context, - ); - - let meta_config = MintActionMetaConfigCpiWrite { - fee_payer: self.payer, - mint_signer: Some(self.mint_signer), - authority: self.params.mint_authority, - cpi_context: self.cpi_context_pubkey, - }; - - let account_metas = get_mint_action_instruction_account_metas_cpi_write(meta_config); - - let data = instruction_data - .data() - .map_err(|e| ProgramError::BorshIoError(e.to_string()))?; - - Ok(Instruction { - program_id: Pubkey::new_from_array(light_token_interface::LIGHT_TOKEN_PROGRAM_ID), - accounts: account_metas, - data, - }) - } -} - // ============================================================================ // AccountInfos Struct: CreateCMintCpi (for CPI usage) // ============================================================================ @@ -451,64 +309,6 @@ impl<'info> TryFrom<&CreateMintCpi<'info>> for CreateMint { } } -// ============================================================================ -// AccountInfos Struct: CreateCompressedMintCpiWriteCpi -// ============================================================================ - -pub struct CreateCompressedMintCpiWriteCpi<'info> { - pub mint_signer: AccountInfo<'info>, - pub authority: AccountInfo<'info>, - pub payer: AccountInfo<'info>, - pub cpi_context_account: AccountInfo<'info>, - pub system_accounts: SystemAccountInfos<'info>, - pub params: CreateMintCpiWriteParams, -} - -impl<'info> CreateCompressedMintCpiWriteCpi<'info> { - pub fn instruction(&self) -> Result { - CreateCompressedMintCpiWrite::try_from(self)?.instruction() - } - - pub fn invoke_signed(self, signer_seeds: &[&[&[u8]]]) -> Result<(), ProgramError> { - let instruction = self.instruction()?; - // Account order must match get_mint_action_instruction_account_metas_cpi_write: - // light_system_program, mint_signer, authority, fee_payer, cpi_authority_pda, cpi_context - let account_infos = [ - self.system_accounts.light_system_program, - self.mint_signer, - self.authority, - self.payer, - self.system_accounts.cpi_authority_pda, - self.cpi_context_account, - ]; - invoke_signed(&instruction, &account_infos, signer_seeds) - } -} - -impl<'info> TryFrom<&CreateCompressedMintCpiWriteCpi<'info>> for CreateCompressedMintCpiWrite { - type Error = ProgramError; - - fn try_from( - account_infos: &CreateCompressedMintCpiWriteCpi<'info>, - ) -> Result { - // Validate that authority account matches params.mint_authority - if account_infos.params.mint_authority != *account_infos.authority.key { - solana_msg::msg!( - "CreateCompressedMintCpiWriteCpi: params.mint_authority ({}) does not match authority account ({})", - account_infos.params.mint_authority, - account_infos.authority.key - ); - return Err(ProgramError::InvalidAccountData); - } - Ok(Self { - mint_signer: *account_infos.mint_signer.key, - payer: *account_infos.payer.key, - cpi_context_pubkey: *account_infos.cpi_context_account.key, - params: account_infos.params.clone(), - }) - } -} - // ============================================================================ // Helper Functions // ============================================================================ diff --git a/sdk-libs/token-sdk/src/token/decompress_mint.rs b/sdk-libs/token-sdk/src/token/decompress_mint.rs index c08b2abbe1..ac79ef9af8 100644 --- a/sdk-libs/token-sdk/src/token/decompress_mint.rs +++ b/sdk-libs/token-sdk/src/token/decompress_mint.rs @@ -10,7 +10,6 @@ use solana_instruction::Instruction; use solana_program_error::ProgramError; use solana_pubkey::Pubkey; -pub use super::find_mint_address; use super::{config_pda, rent_sponsor_pda, SystemAccountInfos}; use crate::compressed_token::mint_action::MintActionMetaConfig; @@ -22,7 +21,6 @@ use crate::compressed_token::mint_action::MintActionMetaConfig; /// # Example /// ```rust,ignore /// let instruction = DecompressMint { -/// mint_seed_pubkey, /// payer, /// authority, /// state_tree, @@ -36,8 +34,6 @@ use crate::compressed_token::mint_action::MintActionMetaConfig; /// ``` #[derive(Debug, Clone)] pub struct DecompressMint { - /// Mint seed pubkey (used to derive CMint PDA) - pub mint_seed_pubkey: Pubkey, /// Fee payer pub payer: Pubkey, /// Mint authority (must sign) @@ -60,8 +56,13 @@ pub struct DecompressMint { impl DecompressMint { pub fn instruction(self) -> Result { - // Derive CMint PDA - let (cmint_pda, _cmint_bump) = find_mint_address(&self.mint_seed_pubkey); + // Get CMint PDA from compressed mint metadata + let mint_data = self + .compressed_mint_with_context + .mint + .as_ref() + .ok_or(ProgramError::InvalidInstructionData)?; + let cmint_pda = Pubkey::from(mint_data.metadata.mint.to_bytes()); // Build DecompressMintAction let action = DecompressMintAction { @@ -113,7 +114,6 @@ impl DecompressMint { /// # Example /// ```rust,ignore /// DecompressMintCpi { -/// mint_seed: mint_seed_account, /// authority: authority_account, /// payer: payer_account, /// cmint: cmint_account, @@ -131,8 +131,6 @@ impl DecompressMint { /// .invoke()?; /// ``` pub struct DecompressMintCpi<'info> { - /// Mint seed account (used to derive CMint PDA, does not sign) - pub mint_seed: AccountInfo<'info>, /// Mint authority (must sign) pub authority: AccountInfo<'info>, /// Fee payer @@ -198,7 +196,6 @@ impl<'info> DecompressMintCpi<'info> { fn build_account_infos(&self) -> Vec> { vec![ self.system_accounts.light_system_program.clone(), - self.mint_seed.clone(), self.authority.clone(), self.compressible_config.clone(), self.cmint.clone(), @@ -221,7 +218,6 @@ impl<'info> TryFrom<&DecompressMintCpi<'info>> for DecompressMint { fn try_from(cpi: &DecompressMintCpi<'info>) -> Result { Ok(Self { - mint_seed_pubkey: *cpi.mint_seed.key, payer: *cpi.payer.key, authority: *cpi.authority.key, state_tree: *cpi.state_tree.key, diff --git a/sdk-tests/csdk-anchor-derived-test/tests/basic_test.rs b/sdk-tests/csdk-anchor-derived-test/tests/basic_test.rs index 38b4f36e78..7a6c487966 100644 --- a/sdk-tests/csdk-anchor-derived-test/tests/basic_test.rs +++ b/sdk-tests/csdk-anchor-derived-test/tests/basic_test.rs @@ -671,7 +671,7 @@ pub async fn create_user_record_and_game_session( version: 3, mint: spl_mint.into(), cmint_decompressed: false, - mint_signer: mint_signer.pubkey().into(), + mint_signer: mint_signer.pubkey().to_bytes(), bump: mint_bump, }, mint_authority: Some(mint_authority.into()), diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/basic_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/basic_test.rs index 6112fa0e70..fbe723e9a6 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/basic_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/basic_test.rs @@ -331,7 +331,7 @@ pub async fn create_user_record_and_game_session( version: 3, mint: spl_mint.into(), cmint_decompressed: false, - mint_signer: mint_signer.pubkey().into(), + mint_signer: mint_signer.pubkey().to_bytes(), bump: mint_bump, }, mint_authority: Some(mint_authority.into()), diff --git a/sdk-tests/sdk-compressible-test/tests/multi_account_tests.rs b/sdk-tests/sdk-compressible-test/tests/multi_account_tests.rs index dff02e01ae..8e037f4533 100644 --- a/sdk-tests/sdk-compressible-test/tests/multi_account_tests.rs +++ b/sdk-tests/sdk-compressible-test/tests/multi_account_tests.rs @@ -332,7 +332,7 @@ pub async fn create_user_record_and_game_session( version: 3, mint: spl_mint.into(), cmint_decompressed: false, - mint_signer: mint_signer.pubkey().into(), + mint_signer: mint_signer.pubkey().to_bytes(), bump: mint_bump, }, mint_authority: Some(mint_authority.into()), diff --git a/sdk-tests/sdk-light-token-test/src/create_cmint.rs b/sdk-tests/sdk-light-token-test/src/create_cmint.rs index 294d81e322..d2a305f491 100644 --- a/sdk-tests/sdk-light-token-test/src/create_cmint.rs +++ b/sdk-tests/sdk-light-token-test/src/create_cmint.rs @@ -19,6 +19,7 @@ pub struct CreateCmintData { pub proof: CompressedProof, pub compression_address: [u8; 32], pub mint: Pubkey, + pub bump: u8, pub freeze_authority: Option, pub extensions: Option>, } @@ -60,6 +61,7 @@ pub fn process_create_cmint( proof: data.proof, compression_address: data.compression_address, mint: data.mint, + bump: data.bump, freeze_authority: data.freeze_authority, extensions: data.extensions, }; @@ -135,6 +137,7 @@ pub fn process_create_cmint_invoke_signed( proof: data.proof, compression_address: data.compression_address, mint: data.mint, + bump: data.bump, freeze_authority: data.freeze_authority, extensions: data.extensions, }; @@ -220,10 +223,11 @@ pub fn process_create_cmint_with_pda_authority( let params = CreateMintParams { decimals: data.decimals, address_merkle_tree_root_index: data.address_merkle_tree_root_index, - mint_authority: authority_pda, // Use the derived PDA as authority + mint_authority: authority_pda, proof: data.proof, compression_address: data.compression_address, mint: data.mint, + bump: data.bump, freeze_authority: data.freeze_authority, extensions: data.extensions, }; diff --git a/sdk-tests/sdk-light-token-test/src/decompress_cmint.rs b/sdk-tests/sdk-light-token-test/src/decompress_cmint.rs index 7f9301eca5..92ac319e89 100644 --- a/sdk-tests/sdk-light-token-test/src/decompress_cmint.rs +++ b/sdk-tests/sdk-light-token-test/src/decompress_cmint.rs @@ -46,30 +46,29 @@ pub fn process_decompress_cmint_invoke_signed( let (pda, bump) = Pubkey::find_program_address(&[MINT_AUTHORITY_SEED], &ID); // Verify the authority account is the PDA we expect - if &pda != accounts[1].key { + if &pda != accounts[0].key { return Err(ProgramError::InvalidSeeds); } let system_accounts = SystemAccountInfos { - light_system_program: accounts[9].clone(), - cpi_authority_pda: accounts[10].clone(), - registered_program_pda: accounts[11].clone(), - account_compression_authority: accounts[12].clone(), - account_compression_program: accounts[13].clone(), - system_program: accounts[14].clone(), + light_system_program: accounts[8].clone(), + cpi_authority_pda: accounts[9].clone(), + registered_program_pda: accounts[10].clone(), + account_compression_authority: accounts[11].clone(), + account_compression_program: accounts[12].clone(), + system_program: accounts[13].clone(), }; let signer_seeds: &[&[u8]] = &[MINT_AUTHORITY_SEED, &[bump]]; DecompressMintCpi { - mint_seed: accounts[0].clone(), - authority: accounts[1].clone(), - payer: accounts[2].clone(), - cmint: accounts[3].clone(), - compressible_config: accounts[4].clone(), - rent_sponsor: accounts[5].clone(), - state_tree: accounts[6].clone(), - input_queue: accounts[7].clone(), - output_queue: accounts[8].clone(), + authority: accounts[0].clone(), + payer: accounts[1].clone(), + cmint: accounts[2].clone(), + compressible_config: accounts[3].clone(), + rent_sponsor: accounts[4].clone(), + state_tree: accounts[5].clone(), + input_queue: accounts[6].clone(), + output_queue: accounts[7].clone(), system_accounts, compressed_mint_with_context: data.compressed_mint_with_context, proof: data.proof, diff --git a/sdk-tests/sdk-light-token-test/tests/shared.rs b/sdk-tests/sdk-light-token-test/tests/shared.rs index e685f24afd..38eb17b3c6 100644 --- a/sdk-tests/sdk-light-token-test/tests/shared.rs +++ b/sdk-tests/sdk-light-token-test/tests/shared.rs @@ -30,7 +30,7 @@ pub async fn setup_create_compressed_mint( &address_tree.tree, ); - let mint = light_token_sdk::token::find_mint_address(&mint_seed.pubkey()).0; + let (mint, bump) = light_token_sdk::token::find_mint_address(&mint_seed.pubkey()); // Get validity proof for the address let rpc_result = rpc @@ -54,6 +54,7 @@ pub async fn setup_create_compressed_mint( proof: rpc_result.proof.0.unwrap(), compression_address, mint, + bump, freeze_authority: None, extensions: None, }; @@ -158,7 +159,6 @@ pub async fn setup_create_compressed_mint( }; let decompress_ix = DecompressMint { - mint_seed_pubkey: mint_seed.pubkey(), payer: payer.pubkey(), authority: mint_authority, state_tree: compressed_mint_account.tree_info.tree, @@ -222,7 +222,7 @@ pub async fn setup_create_compressed_mint_with_freeze_authority( &address_tree.tree, ); - let mint = light_token_sdk::token::find_mint_address(&mint_seed.pubkey()).0; + let (mint, bump) = light_token_sdk::token::find_mint_address(&mint_seed.pubkey()); // Get validity proof for the address let rpc_result = rpc @@ -246,6 +246,7 @@ pub async fn setup_create_compressed_mint_with_freeze_authority( proof: rpc_result.proof.0.unwrap(), compression_address, mint, + bump, freeze_authority, extensions: None, }; @@ -310,7 +311,6 @@ pub async fn setup_create_compressed_mint_with_freeze_authority( }; let decompress_ix = DecompressMint { - mint_seed_pubkey: mint_seed.pubkey(), payer: payer.pubkey(), authority: mint_authority, state_tree: compressed_mint_account.tree_info.tree, @@ -403,7 +403,7 @@ pub async fn setup_create_compressed_mint_with_compression_only( &address_tree.tree, ); - let mint = light_token_sdk::token::find_mint_address(&mint_seed.pubkey()).0; + let (mint, bump) = light_token_sdk::token::find_mint_address(&mint_seed.pubkey()); // Get validity proof for the address let rpc_result = rpc @@ -427,6 +427,7 @@ pub async fn setup_create_compressed_mint_with_compression_only( proof: rpc_result.proof.0.unwrap(), compression_address, mint, + bump, freeze_authority: None, extensions: None, }; @@ -538,7 +539,6 @@ pub async fn setup_create_compressed_mint_with_compression_only( }; let decompress_ix = DecompressMint { - mint_seed_pubkey: mint_seed.pubkey(), payer: payer.pubkey(), authority: mint_authority, state_tree: compressed_mint_account.tree_info.tree, diff --git a/sdk-tests/sdk-light-token-test/tests/test_create_cmint.rs b/sdk-tests/sdk-light-token-test/tests/test_create_cmint.rs index 205eb581ee..828b936353 100644 --- a/sdk-tests/sdk-light-token-test/tests/test_create_cmint.rs +++ b/sdk-tests/sdk-light-token-test/tests/test_create_cmint.rs @@ -47,7 +47,7 @@ async fn test_create_compressed_mint() { &address_tree.tree, ); - let mint_pda = light_token_sdk::token::find_mint_address(&mint_signer.pubkey()).0; + let (mint_pda, mint_bump) = light_token_sdk::token::find_mint_address(&mint_signer.pubkey()); let rpc_result = rpc .get_validity_proof( @@ -70,6 +70,7 @@ async fn test_create_compressed_mint() { proof: rpc_result.proof.0.unwrap(), compression_address, mint: mint_pda, + bump: mint_bump, freeze_authority: None, extensions: Some(vec![ExtensionInstructionData::TokenMetadata( TokenMetadataInstructionData { @@ -155,7 +156,7 @@ async fn test_create_compressed_mint_invoke_signed() { &address_tree.tree, ); - let mint_pda = light_token_sdk::token::find_mint_address(&mint_signer_pda).0; + let (mint_pda, mint_bump) = light_token_sdk::token::find_mint_address(&mint_signer_pda); let rpc_result = rpc .get_validity_proof( @@ -178,6 +179,7 @@ async fn test_create_compressed_mint_invoke_signed() { proof: rpc_result.proof.0.unwrap(), compression_address, mint: mint_pda, + bump: mint_bump, freeze_authority: None, extensions: None, }; diff --git a/sdk-tests/sdk-light-token-test/tests/test_ctoken_mint_to.rs b/sdk-tests/sdk-light-token-test/tests/test_ctoken_mint_to.rs index 43a9ae1cce..9db844b0df 100644 --- a/sdk-tests/sdk-light-token-test/tests/test_ctoken_mint_to.rs +++ b/sdk-tests/sdk-light-token-test/tests/test_ctoken_mint_to.rs @@ -117,7 +117,7 @@ async fn test_ctoken_mint_to_invoke_signed() { &address_tree.tree, ); - let mint_pda = light_token_sdk::token::find_mint_address(&mint_signer_pda).0; + let (mint_pda, mint_bump) = light_token_sdk::token::find_mint_address(&mint_signer_pda); // Step 1: Create compressed mint with PDA authority using wrapper program (discriminator 14) { @@ -145,6 +145,7 @@ async fn test_ctoken_mint_to_invoke_signed() { proof: rpc_result.proof.0.unwrap(), compression_address, mint: mint_pda, + bump: mint_bump, freeze_authority: None, extensions: None, }; diff --git a/sdk-tests/sdk-light-token-test/tests/test_decompress_cmint.rs b/sdk-tests/sdk-light-token-test/tests/test_decompress_cmint.rs index 7b0129b43c..73a21d6516 100644 --- a/sdk-tests/sdk-light-token-test/tests/test_decompress_cmint.rs +++ b/sdk-tests/sdk-light-token-test/tests/test_decompress_cmint.rs @@ -23,7 +23,7 @@ async fn test_decompress_cmint() { let decimals = 9u8; // Create a compressed mint (returns mint_seed keypair) - let (mint_pda, compression_address, _, mint_seed) = + let (mint_pda, compression_address, _, _mint_seed) = shared::setup_create_compressed_mint(&mut rpc, &payer, mint_authority, decimals, vec![]) .await; @@ -69,7 +69,6 @@ async fn test_decompress_cmint() { // Build and execute DecompressMint instruction let decompress_ix = DecompressMint { - mint_seed_pubkey: mint_seed.pubkey(), payer: payer.pubkey(), authority: mint_authority, state_tree: compressed_account.tree_info.tree, @@ -125,7 +124,7 @@ async fn test_decompress_cmint_with_freeze_authority() { let decimals = 6u8; // Create a compressed mint with freeze_authority - let (mint_pda, compression_address, mint_seed) = + let (mint_pda, compression_address, _mint_seed) = setup_create_compressed_mint_with_freeze_authority_only( &mut rpc, &payer, @@ -177,7 +176,6 @@ async fn test_decompress_cmint_with_freeze_authority() { // Build and execute DecompressMint instruction let decompress_ix = DecompressMint { - mint_seed_pubkey: mint_seed.pubkey(), payer: payer.pubkey(), authority: mint_authority, state_tree: compressed_account.tree_info.tree, @@ -240,7 +238,7 @@ async fn setup_create_compressed_mint_with_freeze_authority_only( &address_tree.tree, ); - let mint = find_mint_address(&mint_seed.pubkey()).0; + let (mint, bump) = find_mint_address(&mint_seed.pubkey()); // Get validity proof for the address let rpc_result = rpc @@ -264,6 +262,7 @@ async fn setup_create_compressed_mint_with_freeze_authority_only( proof: rpc_result.proof.0.unwrap(), compression_address, mint, + bump, freeze_authority, extensions: None, }; @@ -324,7 +323,7 @@ async fn test_decompress_cmint_with_token_metadata() { let extensions = vec![ExtensionInstructionData::TokenMetadata(token_metadata)]; // Create a compressed mint with TokenMetadata extension - let (mint_pda, compression_address, mint_seed) = setup_create_compressed_mint_with_extensions( + let (mint_pda, compression_address, _mint_seed) = setup_create_compressed_mint_with_extensions( &mut rpc, &payer, mint_authority, @@ -376,7 +375,6 @@ async fn test_decompress_cmint_with_token_metadata() { // Build and execute DecompressMint instruction let decompress_ix = DecompressMint { - mint_seed_pubkey: mint_seed.pubkey(), payer: payer.pubkey(), authority: mint_authority, state_tree: compressed_account.tree_info.tree, @@ -447,7 +445,7 @@ async fn setup_create_compressed_mint_with_extensions( &address_tree.tree, ); - let mint = find_mint_address(&mint_seed.pubkey()).0; + let (mint, bump) = find_mint_address(&mint_seed.pubkey()); // Get validity proof for the address let rpc_result = rpc @@ -471,6 +469,7 @@ async fn setup_create_compressed_mint_with_extensions( proof: rpc_result.proof.0.unwrap(), compression_address, mint, + bump, freeze_authority, extensions: Some(extensions), }; @@ -533,7 +532,7 @@ async fn test_decompress_cmint_cpi_invoke_signed() { &address_tree.tree, ); - let mint_pda = find_mint_address(&mint_signer_pda).0; + let (mint_pda, mint_bump) = find_mint_address(&mint_signer_pda); // Step 1: Create compressed mint with PDA authority using wrapper program (discriminator 14) { @@ -561,6 +560,7 @@ async fn test_decompress_cmint_cpi_invoke_signed() { proof: rpc_result.proof.0.unwrap(), compression_address, mint: mint_pda, + bump: mint_bump, freeze_authority: None, extensions: None, }; diff --git a/sdk-tests/sdk-token-test/tests/ctoken_pda.rs b/sdk-tests/sdk-token-test/tests/ctoken_pda.rs index 56bf3bd5f5..946c72dc4d 100644 --- a/sdk-tests/sdk-token-test/tests/ctoken_pda.rs +++ b/sdk-tests/sdk-token-test/tests/ctoken_pda.rs @@ -202,7 +202,7 @@ pub async fn create_mint( version: 3, mint: mint.into(), cmint_decompressed: false, - mint_signer: mint_seed.pubkey().into(), + mint_signer: mint_seed.pubkey().to_bytes(), bump: mint_bump, }, mint_authority: Some(mint_authority.pubkey().into()), diff --git a/sdk-tests/sdk-token-test/tests/pda_ctoken.rs b/sdk-tests/sdk-token-test/tests/pda_ctoken.rs index 5ab4ed3855..d455c0b2f2 100644 --- a/sdk-tests/sdk-token-test/tests/pda_ctoken.rs +++ b/sdk-tests/sdk-token-test/tests/pda_ctoken.rs @@ -273,7 +273,7 @@ pub async fn create_mint( version: 3, mint: mint.into(), cmint_decompressed: false, - mint_signer: mint_seed.pubkey().into(), + mint_signer: mint_seed.pubkey().to_bytes(), bump: mint_bump, }, mint_authority: Some(mint_authority.pubkey().into()), diff --git a/sdk-tests/sdk-token-test/tests/test_compress_full_and_close.rs b/sdk-tests/sdk-token-test/tests/test_compress_full_and_close.rs index 6addb72e1c..4a0bbc753b 100644 --- a/sdk-tests/sdk-token-test/tests/test_compress_full_and_close.rs +++ b/sdk-tests/sdk-token-test/tests/test_compress_full_and_close.rs @@ -132,7 +132,7 @@ async fn test_compress_full_and_close() { version: 3, mint: mint_pda.into(), cmint_decompressed: false, - mint_signer: mint_signer.pubkey().into(), + mint_signer: mint_signer.pubkey().to_bytes(), bump: mint_bump, }, reserved: [0u8; 16], From f6ee6e2e260855476546c8267c0ca0e15d88e210 Mon Sep 17 00:00:00 2001 From: ananas Date: Thu, 15 Jan 2026 22:39:03 +0000 Subject: [PATCH 3/3] fix ci --- .../src/v3/actions/mint-to-compressed.ts | 2 + js/compressed-token/src/v3/actions/mint-to.ts | 2 + .../src/v3/instructions/create-mint.ts | 11 +--- .../src/v3/instructions/mint-to-compressed.ts | 10 +-- .../src/v3/instructions/mint-to-interface.ts | 2 + .../src/v3/instructions/mint-to.ts | 10 +-- .../src/v3/instructions/update-metadata.ts | 10 +-- .../src/v3/instructions/update-mint.ts | 12 ++-- .../src/v3/layout/layout-mint-action.ts | 6 +- .../src/v3/layout/layout-mint.ts | 35 +++++++++- .../tests/e2e/get-mint-interface.test.ts | 22 +++++++ .../tests/unit/layout-mint.test.ts | 18 ++++++ .../tests/unit/mint-action-layout.test.ts | 27 ++++---- js/compressed-token/tests/unit/serde.test.ts | 64 ++++++++++++++++++- .../src/state/mint/compressed_mint.rs | 20 ++++-- .../token-interface/src/state/mint/top_up.rs | 2 +- .../compressed-token/program/tests/mint.rs | 25 +++++--- sdk-libs/token-sdk/src/token/create_mint.rs | 3 +- .../token-sdk/src/token/decompress_mint.rs | 1 - .../tests/test_ctoken_mint_to.rs | 32 +++++----- .../tests/test_decompress_cmint.rs | 17 ++++- 21 files changed, 233 insertions(+), 98 deletions(-) diff --git a/js/compressed-token/src/v3/actions/mint-to-compressed.ts b/js/compressed-token/src/v3/actions/mint-to-compressed.ts index db09d5bddc..76d2180ce4 100644 --- a/js/compressed-token/src/v3/actions/mint-to-compressed.ts +++ b/js/compressed-token/src/v3/actions/mint-to-compressed.ts @@ -83,6 +83,8 @@ export async function mintToCompressed( splMint: mintInfo.mintContext!.splMint, cmintDecompressed: mintInfo.mintContext!.cmintDecompressed, version: mintInfo.mintContext!.version, + mintSigner: mintInfo.mintContext!.mintSigner, + bump: mintInfo.mintContext!.bump, metadata: mintInfo.tokenMetadata ? { updateAuthority: diff --git a/js/compressed-token/src/v3/actions/mint-to.ts b/js/compressed-token/src/v3/actions/mint-to.ts index 70238a8ee6..93375bb2e4 100644 --- a/js/compressed-token/src/v3/actions/mint-to.ts +++ b/js/compressed-token/src/v3/actions/mint-to.ts @@ -79,6 +79,8 @@ export async function mintTo( splMint: mintInfo.mintContext!.splMint, cmintDecompressed: mintInfo.mintContext!.cmintDecompressed, version: mintInfo.mintContext!.version, + mintSigner: mintInfo.mintContext!.mintSigner, + bump: mintInfo.mintContext!.bump, metadata: mintInfo.tokenMetadata ? { updateAuthority: diff --git a/js/compressed-token/src/v3/instructions/create-mint.ts b/js/compressed-token/src/v3/instructions/create-mint.ts index e8d502f1aa..b91de68252 100644 --- a/js/compressed-token/src/v3/instructions/create-mint.ts +++ b/js/compressed-token/src/v3/instructions/create-mint.ts @@ -9,7 +9,6 @@ import { CTOKEN_PROGRAM_ID, LightSystemProgram, defaultStaticAccountsStruct, - deriveAddressV2, TreeInfo, AddressTreeInfo, ValidityProof, @@ -95,12 +94,7 @@ function validateProofArrays( export function encodeCreateMintInstructionData( params: EncodeCreateMintInstructionParams, ): Buffer { - const [splMintPda] = findMintAddress(params.mintSigner); - const compressedAddress = deriveAddressV2( - splMintPda.toBytes(), - params.addressTree, - CTOKEN_PROGRAM_ID, - ); + const [splMintPda, bump] = findMintAddress(params.mintSigner); // Build extensions if metadata present let extensions: { tokenMetadata: TokenMetadataBorshData }[] | null = null; @@ -141,7 +135,8 @@ export function encodeCreateMintInstructionData( version: TokenDataVersion.ShaFlat, cmintDecompressed: false, mint: splMintPda, - compressedAddress: Array.from(compressedAddress.toBytes()), + mintSigner: Array.from(params.mintSigner.toBytes()), + bump, }, mintAuthority: params.mintAuthority, freezeAuthority: params.freezeAuthority, diff --git a/js/compressed-token/src/v3/instructions/mint-to-compressed.ts b/js/compressed-token/src/v3/instructions/mint-to-compressed.ts index 427cec5af5..109a1c27cf 100644 --- a/js/compressed-token/src/v3/instructions/mint-to-compressed.ts +++ b/js/compressed-token/src/v3/instructions/mint-to-compressed.ts @@ -9,7 +9,6 @@ import { CTOKEN_PROGRAM_ID, LightSystemProgram, defaultStaticAccountsStruct, - deriveAddressV2, getDefaultAddressTreeInfo, MerkleContext, TreeInfo, @@ -36,12 +35,6 @@ interface EncodeCompressedMintToInstructionParams { function encodeCompressedMintToInstructionData( params: EncodeCompressedMintToInstructionParams, ): Buffer { - const compressedAddress = deriveAddressV2( - params.mintData.splMint.toBytes(), - params.addressTree, - CTOKEN_PROGRAM_ID, - ); - // TokenMetadata extension not supported in mintTo instruction if (params.mintData.metadata) { throw new Error( @@ -75,7 +68,8 @@ function encodeCompressedMintToInstructionData( version: params.mintData.version, cmintDecompressed: params.mintData.cmintDecompressed, mint: params.mintData.splMint, - compressedAddress: Array.from(compressedAddress.toBytes()), + mintSigner: Array.from(params.mintData.mintSigner), + bump: params.mintData.bump, }, mintAuthority: params.mintData.mintAuthority, freezeAuthority: params.mintData.freezeAuthority, diff --git a/js/compressed-token/src/v3/instructions/mint-to-interface.ts b/js/compressed-token/src/v3/instructions/mint-to-interface.ts index 004ab494b3..035047230a 100644 --- a/js/compressed-token/src/v3/instructions/mint-to-interface.ts +++ b/js/compressed-token/src/v3/instructions/mint-to-interface.ts @@ -69,6 +69,8 @@ export function createMintToInterfaceInstruction( splMint: mintInterface.mintContext.splMint, cmintDecompressed: mintInterface.mintContext.cmintDecompressed, version: mintInterface.mintContext.version, + mintSigner: mintInterface.mintContext.mintSigner, + bump: mintInterface.mintContext.bump, metadata: mintInterface.tokenMetadata ? { updateAuthority: diff --git a/js/compressed-token/src/v3/instructions/mint-to.ts b/js/compressed-token/src/v3/instructions/mint-to.ts index f72c4a399e..bf41b4bda0 100644 --- a/js/compressed-token/src/v3/instructions/mint-to.ts +++ b/js/compressed-token/src/v3/instructions/mint-to.ts @@ -9,7 +9,6 @@ import { CTOKEN_PROGRAM_ID, LightSystemProgram, defaultStaticAccountsStruct, - deriveAddressV2, getDefaultAddressTreeInfo, MerkleContext, TreeInfo, @@ -34,12 +33,6 @@ interface EncodeMintToCTokenInstructionParams { function encodeMintToCTokenInstructionData( params: EncodeMintToCTokenInstructionParams, ): Buffer { - const compressedAddress = deriveAddressV2( - params.mintData.splMint.toBytes(), - params.addressTree, - CTOKEN_PROGRAM_ID, - ); - // TokenMetadata extension not supported in mintTo instruction if (params.mintData.metadata) { throw new Error( @@ -70,7 +63,8 @@ function encodeMintToCTokenInstructionData( version: params.mintData.version, cmintDecompressed: params.mintData.cmintDecompressed, mint: params.mintData.splMint, - compressedAddress: Array.from(compressedAddress.toBytes()), + mintSigner: Array.from(params.mintData.mintSigner), + bump: params.mintData.bump, }, mintAuthority: params.mintData.mintAuthority, freezeAuthority: params.mintData.freezeAuthority, diff --git a/js/compressed-token/src/v3/instructions/update-metadata.ts b/js/compressed-token/src/v3/instructions/update-metadata.ts index 2673aa22af..f3fc520957 100644 --- a/js/compressed-token/src/v3/instructions/update-metadata.ts +++ b/js/compressed-token/src/v3/instructions/update-metadata.ts @@ -9,7 +9,6 @@ import { CTOKEN_PROGRAM_ID, LightSystemProgram, defaultStaticAccountsStruct, - deriveAddressV2, getDefaultAddressTreeInfo, getOutputQueue, } from '@lightprotocol/stateless.js'; @@ -82,12 +81,6 @@ function convertActionToBorsh(action: UpdateMetadataAction): Action { function encodeUpdateMetadataInstructionData( params: EncodeUpdateMetadataInstructionParams, ): Buffer { - const compressedAddress = deriveAddressV2( - params.splMint.toBytes(), - params.addressTree, - CTOKEN_PROGRAM_ID, - ); - const mintInterface = params.mintInterface; if (!mintInterface.tokenMetadata) { @@ -112,7 +105,8 @@ function encodeUpdateMetadataInstructionData( version: mintInterface.mintContext!.version, cmintDecompressed: mintInterface.mintContext!.cmintDecompressed, mint: mintInterface.mintContext!.splMint, - compressedAddress: Array.from(compressedAddress.toBytes()), + mintSigner: Array.from(mintInterface.mintContext!.mintSigner), + bump: mintInterface.mintContext!.bump, }, mintAuthority: mintInterface.mint.mintAuthority, freezeAuthority: mintInterface.mint.freezeAuthority, diff --git a/js/compressed-token/src/v3/instructions/update-mint.ts b/js/compressed-token/src/v3/instructions/update-mint.ts index be962d804c..c2a28fdab5 100644 --- a/js/compressed-token/src/v3/instructions/update-mint.ts +++ b/js/compressed-token/src/v3/instructions/update-mint.ts @@ -9,7 +9,6 @@ import { CTOKEN_PROGRAM_ID, LightSystemProgram, defaultStaticAccountsStruct, - deriveAddressV2, getDefaultAddressTreeInfo, getOutputQueue, } from '@lightprotocol/stateless.js'; @@ -37,12 +36,6 @@ interface EncodeUpdateMintInstructionParams { function encodeUpdateMintInstructionData( params: EncodeUpdateMintInstructionParams, ): Buffer { - const compressedAddress = deriveAddressV2( - params.splMint.toBytes(), - params.addressTree, - CTOKEN_PROGRAM_ID, - ); - // Build action const action: Action = params.actionType === 'mintAuthority' @@ -86,7 +79,10 @@ function encodeUpdateMintInstructionData( cmintDecompressed: params.mintInterface.mintContext!.cmintDecompressed, mint: params.mintInterface.mintContext!.splMint, - compressedAddress: Array.from(compressedAddress.toBytes()), + mintSigner: Array.from( + params.mintInterface.mintContext!.mintSigner, + ), + bump: params.mintInterface.mintContext!.bump, }, mintAuthority: params.mintInterface.mint.mintAuthority, freezeAuthority: params.mintInterface.mint.freezeAuthority, diff --git a/js/compressed-token/src/v3/layout/layout-mint-action.ts b/js/compressed-token/src/v3/layout/layout-mint-action.ts index 8ad1ec9bc2..6f4bd925c8 100644 --- a/js/compressed-token/src/v3/layout/layout-mint-action.ts +++ b/js/compressed-token/src/v3/layout/layout-mint-action.ts @@ -142,7 +142,8 @@ export const CompressedMintMetadataLayout = struct([ u8('version'), bool('cmintDecompressed'), publicKey('mint'), - array(u8(), 32, 'compressedAddress'), + array(u8(), 32, 'mintSigner'), + u8('bump'), ]); export const CompressedMintInstructionDataLayout = struct([ @@ -309,7 +310,8 @@ export interface CompressedMintMetadata { version: number; cmintDecompressed: boolean; mint: PublicKey; - compressedAddress: number[]; + mintSigner: number[]; + bump: number; } export interface CompressedMintInstructionData { diff --git a/js/compressed-token/src/v3/layout/layout-mint.ts b/js/compressed-token/src/v3/layout/layout-mint.ts index 2fbd990592..8227d8dcd1 100644 --- a/js/compressed-token/src/v3/layout/layout-mint.ts +++ b/js/compressed-token/src/v3/layout/layout-mint.ts @@ -37,6 +37,10 @@ export interface MintContext { cmintDecompressed: boolean; /** PDA of the associated SPL mint */ splMint: PublicKey; + /** Signer pubkey used to derive the mint PDA */ + mintSigner: Uint8Array; + /** Bump seed for the mint PDA */ + bump: number; } /** @@ -95,6 +99,9 @@ export interface CompressedMint { } /** MintContext as stored by the program */ +/** + * Raw mint context for layout encoding (mintSigner and bump are encoded separately) + */ export interface RawMintContext { version: number; cmintDecompressed: number; // bool as u8 @@ -108,11 +115,15 @@ export const MintContextLayout = struct([ publicKey('splMint'), ]); -/** Byte length of MintContext */ +/** Byte length of MintContext (excluding mintSigner and bump which are read separately) */ export const MINT_CONTEXT_SIZE = MintContextLayout.span; // 34 bytes -/** Reserved bytes for T22 layout compatibility */ -export const RESERVED_SIZE = 49; +/** Additional bytes for mintSigner (32) + bump (1) */ +export const MINT_SIGNER_SIZE = 32; +export const BUMP_SIZE = 1; + +/** Reserved bytes for T22 layout compatibility (padding to reach byte 165) */ +export const RESERVED_SIZE = 16; /** Account type discriminator size */ export const ACCOUNT_TYPE_SIZE = 1; @@ -323,6 +334,12 @@ export function deserializeMint(data: Buffer | Uint8Array): CompressedMint { ); offset += MINT_CONTEXT_SIZE; + // 2b. Read mintSigner (32 bytes) and bump (1 byte) + const mintSigner = buffer.slice(offset, offset + MINT_SIGNER_SIZE); + offset += MINT_SIGNER_SIZE; + const bump = buffer.readUInt8(offset); + offset += BUMP_SIZE; + // 3. Read reserved bytes (49 bytes) for T22 compatibility const reserved = buffer.slice(offset, offset + RESERVED_SIZE); offset += RESERVED_SIZE; @@ -386,6 +403,8 @@ export function deserializeMint(data: Buffer | Uint8Array): CompressedMint { version: rawContext.version, cmintDecompressed: rawContext.cmintDecompressed !== 0, splMint: rawContext.splMint, + mintSigner, + bump, }; const mint: CompressedMint = { @@ -487,6 +506,10 @@ export function serializeMint(mint: CompressedMint): Buffer { ); buffers.push(contextBuffer); + // 2b. Encode mintSigner (32 bytes) and bump (1 byte) + buffers.push(Buffer.from(mint.mintContext.mintSigner)); + buffers.push(Buffer.from([mint.mintContext.bump])); + // 3. Encode reserved bytes (49 bytes) - default to zeros const reserved = mint.reserved ?? new Uint8Array(RESERVED_SIZE); buffers.push(Buffer.from(reserved)); @@ -663,6 +686,10 @@ export interface MintInstructionData { splMint: PublicKey; cmintDecompressed: boolean; version: number; + /** Signer pubkey used to derive the mint PDA */ + mintSigner: Uint8Array; + /** Bump seed for the mint PDA */ + bump: number; metadata?: MintMetadataField; } @@ -705,6 +732,8 @@ export function toMintInstructionData( splMint: mintContext.splMint, cmintDecompressed: mintContext.cmintDecompressed, version: mintContext.version, + mintSigner: mintContext.mintSigner, + bump: mintContext.bump, metadata, }; } diff --git a/js/compressed-token/tests/e2e/get-mint-interface.test.ts b/js/compressed-token/tests/e2e/get-mint-interface.test.ts index c06745e8ca..61771fa715 100644 --- a/js/compressed-token/tests/e2e/get-mint-interface.test.ts +++ b/js/compressed-token/tests/e2e/get-mint-interface.test.ts @@ -563,6 +563,8 @@ describe('unpackMintInterface', () => { version: 1, cmintDecompressed: true, splMint, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: null, }; @@ -620,6 +622,8 @@ describe('unpackMintInterface', () => { version: 1, cmintDecompressed: false, splMint, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: [ { @@ -665,6 +669,8 @@ describe('unpackMintInterface', () => { version: 1, cmintDecompressed: false, splMint: PublicKey.default, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: null, }; @@ -695,6 +701,8 @@ describe('unpackMintInterface', () => { version: 1, cmintDecompressed: false, splMint: PublicKey.default, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: null, }; @@ -762,6 +770,8 @@ describe('unpackMintData', () => { version: 1, cmintDecompressed: true, splMint, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: null, }; @@ -802,6 +812,8 @@ describe('unpackMintData', () => { version: 1, cmintDecompressed: false, splMint: PublicKey.default, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: [ { @@ -853,6 +865,8 @@ describe('unpackMintData', () => { version: 1, cmintDecompressed: false, splMint: PublicKey.default, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: [ { @@ -885,6 +899,8 @@ describe('unpackMintData', () => { version: 2, cmintDecompressed: false, splMint: PublicKey.default, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: null, }; @@ -912,6 +928,8 @@ describe('unpackMintData', () => { version, cmintDecompressed: false, splMint: PublicKey.default, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: null, }; @@ -937,6 +955,8 @@ describe('unpackMintData', () => { version: 1, cmintDecompressed: initialized, splMint: PublicKey.default, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: null, }; @@ -970,6 +990,8 @@ describe('unpackMintData', () => { version: 1, cmintDecompressed: false, splMint: PublicKey.default, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: [ { diff --git a/js/compressed-token/tests/unit/layout-mint.test.ts b/js/compressed-token/tests/unit/layout-mint.test.ts index 0079281c7b..55f199809f 100644 --- a/js/compressed-token/tests/unit/layout-mint.test.ts +++ b/js/compressed-token/tests/unit/layout-mint.test.ts @@ -34,6 +34,8 @@ describe('layout-mint', () => { version: 1, cmintDecompressed: true, splMint, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: null, }; @@ -73,6 +75,8 @@ describe('layout-mint', () => { version: 0, cmintDecompressed: false, splMint, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: null, }; @@ -101,6 +105,8 @@ describe('layout-mint', () => { version: 1, cmintDecompressed: true, splMint, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: null, }; @@ -140,6 +146,8 @@ describe('layout-mint', () => { version: 1, cmintDecompressed: true, splMint, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: [ { @@ -175,6 +183,8 @@ describe('layout-mint', () => { version: 1, cmintDecompressed: true, splMint, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: null, }; @@ -355,6 +365,8 @@ describe('layout-mint', () => { version: 1, cmintDecompressed: true, splMint, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: null, }; @@ -398,6 +410,8 @@ describe('layout-mint', () => { version: 1, cmintDecompressed: true, splMint, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: [ { @@ -444,6 +458,8 @@ describe('layout-mint', () => { version: 1, cmintDecompressed: true, splMint, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: [ { @@ -476,6 +492,8 @@ describe('layout-mint', () => { version: 1, cmintDecompressed: true, splMint, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: null, }; diff --git a/js/compressed-token/tests/unit/mint-action-layout.test.ts b/js/compressed-token/tests/unit/mint-action-layout.test.ts index 2e124393eb..231a6a401d 100644 --- a/js/compressed-token/tests/unit/mint-action-layout.test.ts +++ b/js/compressed-token/tests/unit/mint-action-layout.test.ts @@ -46,9 +46,8 @@ describe('MintActionCompressedInstructionData Layout', () => { version: TokenDataVersion.ShaFlat, cmintDecompressed: false, mint: mintSigner.publicKey, - compressedAddress: Array.from( - new Uint8Array(32).fill(1), - ), + mintSigner: Array.from(new Uint8Array(32).fill(1)), + bump: 254, }, mintAuthority: mintAuthority.publicKey, freezeAuthority: null, @@ -72,8 +71,8 @@ describe('MintActionCompressedInstructionData Layout', () => { expect(decoded.cpiContext).toBeNull(); expect(decoded.mint).toBeDefined(); expect(decoded.mint!.decimals).toBe(9); - expect(decoded.mint!.metadata.compressedAddress).toEqual( - instructionData.mint!.metadata.compressedAddress, + expect(decoded.mint!.metadata.mintSigner).toEqual( + instructionData.mint!.metadata.mintSigner, ); }); @@ -100,9 +99,8 @@ describe('MintActionCompressedInstructionData Layout', () => { version: TokenDataVersion.ShaFlat, cmintDecompressed: false, mint: mintSigner.publicKey, - compressedAddress: Array.from( - new Uint8Array(32).fill(0), - ), + mintSigner: Array.from(new Uint8Array(32).fill(0)), + bump: 254, }, mintAuthority: mintAuthority.publicKey, freezeAuthority: null, @@ -145,9 +143,8 @@ describe('MintActionCompressedInstructionData Layout', () => { version: TokenDataVersion.ShaFlat, cmintDecompressed: false, mint: mintSigner.publicKey, - compressedAddress: Array.from( - new Uint8Array(32).fill(5), - ), + mintSigner: Array.from(new Uint8Array(32).fill(5)), + bump: 254, }, mintAuthority: mintAuthority.publicKey, freezeAuthority: freezeAuthority.publicKey, @@ -191,9 +188,8 @@ describe('MintActionCompressedInstructionData Layout', () => { version: TokenDataVersion.ShaFlat, cmintDecompressed: false, mint: mintSigner.publicKey, - compressedAddress: Array.from( - new Uint8Array(32).fill(6), - ), + mintSigner: Array.from(new Uint8Array(32).fill(6)), + bump: 254, }, mintAuthority: mintAuthority.publicKey, freezeAuthority: null, @@ -249,7 +245,8 @@ describe('MintActionCompressedInstructionData Layout', () => { version: 0, cmintDecompressed: false, mint: mintSigner, - compressedAddress: Array(32).fill(0), + mintSigner: Array(32).fill(0), + bump: 254, }, mintAuthority: mintAuthority, freezeAuthority: null, diff --git a/js/compressed-token/tests/unit/serde.test.ts b/js/compressed-token/tests/unit/serde.test.ts index b22792ed74..233418de2d 100644 --- a/js/compressed-token/tests/unit/serde.test.ts +++ b/js/compressed-token/tests/unit/serde.test.ts @@ -24,6 +24,8 @@ import { RESERVED_SIZE, ACCOUNT_TYPE_SIZE, COMPRESSION_INFO_SIZE, + MINT_SIGNER_SIZE, + BUMP_SIZE, } from '../../src/v3'; import { MINT_SIZE } from '@solana/spl-token'; @@ -51,6 +53,8 @@ describe('serde', () => { version: 1, cmintDecompressed: false, splMint: PublicKey.default, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: null, }, @@ -69,6 +73,8 @@ describe('serde', () => { version: 1, cmintDecompressed: true, splMint: Keypair.generate().publicKey, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: null, }, @@ -87,6 +93,8 @@ describe('serde', () => { version: 0, cmintDecompressed: false, splMint: PublicKey.default, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: null, }, @@ -105,6 +113,8 @@ describe('serde', () => { version: 255, cmintDecompressed: true, splMint: Keypair.generate().publicKey, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: null, }, @@ -123,6 +133,8 @@ describe('serde', () => { version: 0, cmintDecompressed: false, splMint: PublicKey.default, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: null, }, @@ -186,15 +198,19 @@ describe('serde', () => { version: 1, cmintDecompressed: false, splMint: PublicKey.default, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: null, }; const serialized = serializeMint(mint); - // 82 (MINT_SIZE) + 34 (MINT_CONTEXT_SIZE) + 49 (RESERVED_SIZE) + 1 (ACCOUNT_TYPE_SIZE) + 88 (COMPRESSION_INFO_SIZE) + 1 (None option byte) + // 82 (MINT_SIZE) + 34 (MINT_CONTEXT_SIZE) + 32 (MINT_SIGNER_SIZE) + 1 (BUMP_SIZE) + 16 (RESERVED_SIZE) + 1 (ACCOUNT_TYPE_SIZE) + 96 (COMPRESSION_INFO_SIZE) + 1 (None option byte) const baseSize = MINT_SIZE + MINT_CONTEXT_SIZE + + MINT_SIGNER_SIZE + + BUMP_SIZE + RESERVED_SIZE + ACCOUNT_TYPE_SIZE + COMPRESSION_INFO_SIZE; @@ -217,6 +233,8 @@ describe('serde', () => { version: 1, cmintDecompressed: false, splMint: PublicKey.default, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: [ { @@ -232,6 +250,8 @@ describe('serde', () => { const baseSize = MINT_SIZE + MINT_CONTEXT_SIZE + + MINT_SIGNER_SIZE + + BUMP_SIZE + RESERVED_SIZE + ACCOUNT_TYPE_SIZE + COMPRESSION_INFO_SIZE; @@ -254,6 +274,8 @@ describe('serde', () => { version: 1, cmintDecompressed: false, splMint: PublicKey.default, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: [ { extensionType: 1, data: ext1Data }, @@ -267,6 +289,8 @@ describe('serde', () => { const baseSize = MINT_SIZE + MINT_CONTEXT_SIZE + + MINT_SIGNER_SIZE + + BUMP_SIZE + RESERVED_SIZE + ACCOUNT_TYPE_SIZE + COMPRESSION_INFO_SIZE; @@ -287,6 +311,8 @@ describe('serde', () => { version: 1, cmintDecompressed: false, splMint: PublicKey.default, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: [], }; @@ -297,6 +323,8 @@ describe('serde', () => { const baseSize = MINT_SIZE + MINT_CONTEXT_SIZE + + MINT_SIGNER_SIZE + + BUMP_SIZE + RESERVED_SIZE + ACCOUNT_TYPE_SIZE + COMPRESSION_INFO_SIZE; @@ -522,6 +550,8 @@ describe('serde', () => { version: 1, cmintDecompressed: false, splMint: PublicKey.default, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: null, }; @@ -550,6 +580,8 @@ describe('serde', () => { version, cmintDecompressed: false, splMint: PublicKey.default, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: null, }; @@ -575,6 +607,8 @@ describe('serde', () => { version: 1, cmintDecompressed: initialized, splMint: PublicKey.default, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: null, }; @@ -611,6 +645,8 @@ describe('serde', () => { version: 1, cmintDecompressed: true, splMint: pubkey, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: null, }; @@ -645,6 +681,8 @@ describe('serde', () => { version: 1, cmintDecompressed: false, splMint: PublicKey.default, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: null, }; @@ -675,6 +713,8 @@ describe('serde', () => { version: 1, cmintDecompressed: false, splMint: PublicKey.default, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: null, }; @@ -702,6 +742,8 @@ describe('serde', () => { version: 1, cmintDecompressed: false, splMint: PublicKey.default, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: null, }; @@ -737,6 +779,8 @@ describe('serde', () => { version: 1, cmintDecompressed: true, splMint: Keypair.generate().publicKey, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: [ { @@ -783,6 +827,8 @@ describe('serde', () => { version: 1, cmintDecompressed: false, splMint: PublicKey.default, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: null, }; @@ -807,6 +853,8 @@ describe('serde', () => { // Build buffer in Borsh format manually (includes new fields) const baseMintBuffer = Buffer.alloc(MINT_SIZE); const contextBuffer = Buffer.alloc(MINT_CONTEXT_SIZE); + const mintSignerBuffer = Buffer.alloc(MINT_SIGNER_SIZE); // 32 bytes for mintSigner + const bumpBuffer = Buffer.from([254]); // 1 byte for bump const reservedBuffer = Buffer.alloc(RESERVED_SIZE); const accountTypeBuffer = Buffer.from([1]); // ACCOUNT_TYPE_MINT = 1 const compressionBuffer = Buffer.alloc(COMPRESSION_INFO_SIZE); @@ -822,6 +870,8 @@ describe('serde', () => { const fullBuffer = Buffer.concat([ baseMintBuffer, contextBuffer, + mintSignerBuffer, + bumpBuffer, reservedBuffer, accountTypeBuffer, compressionBuffer, @@ -872,6 +922,8 @@ describe('serde', () => { version: 1, cmintDecompressed: false, splMint: PublicKey.default, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: [ { @@ -1071,6 +1123,8 @@ describe('serde', () => { version: 1, cmintDecompressed: true, splMint, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: null, }; @@ -1113,6 +1167,8 @@ describe('serde', () => { version: 2, cmintDecompressed: false, splMint, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: [ { @@ -1151,6 +1207,8 @@ describe('serde', () => { version: 1, cmintDecompressed: false, splMint: PublicKey.default, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: [], }; @@ -1179,6 +1237,8 @@ describe('serde', () => { version: 1, cmintDecompressed: false, splMint: PublicKey.default, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: [ { @@ -1216,6 +1276,8 @@ describe('serde', () => { version: 1, cmintDecompressed: true, splMint: Keypair.generate().publicKey, + mintSigner: new Uint8Array(32), + bump: 254, }, extensions: [ { diff --git a/program-libs/token-interface/src/state/mint/compressed_mint.rs b/program-libs/token-interface/src/state/mint/compressed_mint.rs index f4434dd6f0..b499471856 100644 --- a/program-libs/token-interface/src/state/mint/compressed_mint.rs +++ b/program-libs/token-interface/src/state/mint/compressed_mint.rs @@ -20,9 +20,10 @@ pub const ACCOUNT_TYPE_MINT: u8 = 1; pub struct CompressedMint { pub base: BaseMint, pub metadata: CompressedMintMetadata, - /// Reserved bytes for T22 layout compatibility (padding to reach byte 165) + /// Reserved bytes (16 bytes) for T22 layout compatibility. + /// Positions `account_type` at offset 165: 82 (BaseMint) + 67 (metadata) + 16 (reserved) = 165. pub reserved: [u8; 16], - /// Account type discriminator at byte 165 (1 = Mint, 2 = Account) + /// Account type discriminator at byte offset 165 (1 = Mint, 2 = Account) pub account_type: u8, /// Compression info embedded directly in the mint pub compression: CompressionInfo, @@ -62,7 +63,14 @@ pub struct BaseMint { pub freeze_authority: Option, } -/// Light Protocol-specific metadata for compressed mints +/// Light Protocol-specific metadata for compressed mints. +/// +/// Total size: 67 bytes +/// - version: 1 byte +/// - cmint_decompressed: 1 byte +/// - mint: 32 bytes +/// - mint_signer: 32 bytes +/// - bump: 1 byte #[repr(C)] #[derive( Debug, Default, PartialEq, Eq, Clone, AnchorDeserialize, AnchorSerialize, ZeroCopyMut, ZeroCopy, @@ -73,11 +81,11 @@ pub struct CompressedMintMetadata { /// Whether the compressed mint has been decompressed to a CMint Solana account. /// When true, the CMint account is the source of truth. pub cmint_decompressed: bool, - /// Pda with seed address of compressed mint + /// PDA derived from mint_signer, used as seed for the compressed address pub mint: Pubkey, - /// Signer pubkey used to derive the compressed address + /// Signer pubkey used to derive the mint PDA pub mint_signer: [u8; 32], - /// Bump seed used for compressed address derivation + /// Bump seed from mint PDA derivation pub bump: u8, } diff --git a/program-libs/token-interface/src/state/mint/top_up.rs b/program-libs/token-interface/src/state/mint/top_up.rs index 8ac86db8aa..e054892249 100644 --- a/program-libs/token-interface/src/state/mint/top_up.rs +++ b/program-libs/token-interface/src/state/mint/top_up.rs @@ -13,7 +13,7 @@ use super::compressed_mint::ACCOUNT_TYPE_MINT; pub const CMINT_MIN_SIZE_WITH_COMPRESSION: usize = COMPRESSION_INFO_OFFSET + COMPRESSION_INFO_SIZE; /// Offset to CompressionInfo in CMint. -/// 82 (BaseMint) + 66 (metadata) + 17 (reserved) + 1 (account_type) = 166 +/// 82 (BaseMint) + 67 (metadata) + 16 (reserved) + 1 (account_type) = 166 const COMPRESSION_INFO_OFFSET: usize = 166; /// Size of CompressionInfo struct (96 bytes). diff --git a/programs/compressed-token/program/tests/mint.rs b/programs/compressed-token/program/tests/mint.rs index 6525fdd89a..6a1a720313 100644 --- a/programs/compressed-token/program/tests/mint.rs +++ b/programs/compressed-token/program/tests/mint.rs @@ -20,9 +20,11 @@ use light_token_interface::{ CompressedMintMetadata, CompressionInfo, ExtensionStruct, TokenMetadata, ZCompressedMint, ZExtensionStruct, ACCOUNT_TYPE_MINT, }, + CMINT_ADDRESS_TREE, COMPRESSED_MINT_SEED, LIGHT_TOKEN_PROGRAM_ID, }; use light_zero_copy::{traits::ZeroCopyAt, ZeroCopyNew}; use rand::Rng; +use solana_pubkey::Pubkey as SolanaPubkey; #[test] fn test_rnd_create_compressed_mint_account() { @@ -33,10 +35,16 @@ fn test_rnd_create_compressed_mint_account() { println!("\n=== TEST ITERATION {} ===", i + 1); // Generate random mint parameters - let mint_pda = Pubkey::new_from_array(rng.gen::<[u8; 32]>()); + // mint_signer is the seed used to derive the mint PDA + let mint_signer_bytes: [u8; 32] = rng.gen(); + let mint_signer = Pubkey::new_from_array(mint_signer_bytes); + // Derive mint_pda and bump from mint_signer using the same PDA derivation as production + let (solana_mint_pda, bump) = SolanaPubkey::find_program_address( + &[COMPRESSED_MINT_SEED, &mint_signer_bytes], + &SolanaPubkey::new_from_array(LIGHT_TOKEN_PROGRAM_ID), + ); + let mint_pda = Pubkey::new_from_array(solana_mint_pda.to_bytes()); let decimals = rng.gen_range(0..=18u8); - let program_id: Pubkey = light_compressed_token::ID.into(); - let address_merkle_tree = Pubkey::new_from_array(rng.gen::<[u8; 32]>()); // Random freeze authority (50% chance) let freeze_authority = if rng.gen_bool(0.5) { @@ -61,13 +69,12 @@ fn test_rnd_create_compressed_mint_account() { let leaf_index = rng.gen::(); let prove_by_index = rng.gen_bool(0.5); let root_index = rng.gen::(); - let _output_merkle_tree_index = rng.gen_range(0..=255u8); - // Derive compressed account address + // Derive compressed account address using the same constants as compressed_address() method let compressed_account_address = derive_address( &mint_pda.to_bytes(), - &address_merkle_tree.to_bytes(), - &program_id.to_bytes(), + &CMINT_ADDRESS_TREE, + &LIGHT_TOKEN_PROGRAM_ID, ); // Step 1: Create random extension data (simplified for current API) @@ -112,9 +119,7 @@ fn test_rnd_create_compressed_mint_account() { }; // Step 2: Create CompressedMintInstructionData using current API - // mint_signer is a random pubkey used as seed for the mint PDA - let mint_signer = Pubkey::new_from_array(rng.gen::<[u8; 32]>()); - let bump: u8 = rng.gen(); + // mint_signer and bump were derived at the start of the iteration let mint_instruction_data = CompressedMintInstructionData { supply: input_supply, decimals, diff --git a/sdk-libs/token-sdk/src/token/create_mint.rs b/sdk-libs/token-sdk/src/token/create_mint.rs index 3e19525ad9..5b20117b90 100644 --- a/sdk-libs/token-sdk/src/token/create_mint.rs +++ b/sdk-libs/token-sdk/src/token/create_mint.rs @@ -47,7 +47,7 @@ pub struct CreateMintParams { /// /// // Derive addresses /// let compression_address = derive_mint_compressed_address(&mint_seed_pubkey, &address_tree); -/// let mint = find_mint_address(&mint_seed_pubkey).0; +/// let (mint, bump) = find_mint_address(&mint_seed_pubkey); /// /// let params = CreateMintParams { /// decimals: 9, @@ -56,6 +56,7 @@ pub struct CreateMintParams { /// proof, // from rpc.get_validity_proof /// compression_address, /// mint, +/// bump, /// freeze_authority: None, /// extensions: None, /// }; diff --git a/sdk-libs/token-sdk/src/token/decompress_mint.rs b/sdk-libs/token-sdk/src/token/decompress_mint.rs index ac79ef9af8..35eb188483 100644 --- a/sdk-libs/token-sdk/src/token/decompress_mint.rs +++ b/sdk-libs/token-sdk/src/token/decompress_mint.rs @@ -78,7 +78,6 @@ impl DecompressMint { .with_decompress_mint(action); // Build account metas with compressible CMint - // Note: mint_signer is NOT needed for decompress_mint - it uses compressed_mint.metadata.mint_signer let meta_config = MintActionMetaConfig::new( self.payer, self.authority, diff --git a/sdk-tests/sdk-light-token-test/tests/test_ctoken_mint_to.rs b/sdk-tests/sdk-light-token-test/tests/test_ctoken_mint_to.rs index 9db844b0df..6f0667eaa7 100644 --- a/sdk-tests/sdk-light-token-test/tests/test_ctoken_mint_to.rs +++ b/sdk-tests/sdk-light-token-test/tests/test_ctoken_mint_to.rs @@ -234,26 +234,24 @@ async fn test_ctoken_mint_to_invoke_signed() { .concat(); // Account order matches process_decompress_cmint_invoke_signed: - // 0: mint_seed (readonly) - // 1: authority (PDA, readonly - program signs) - // 2: payer (signer, writable) - // 3: cmint (writable) - // 4: compressible_config (readonly) - // 5: rent_sponsor (writable) - // 6: state_tree (writable) - // 7: input_queue (writable) - // 8: output_queue (writable) - // 9: light_system_program (readonly) - // 10: cpi_authority_pda (readonly) - // 11: registered_program_pda (readonly) - // 12: account_compression_authority (readonly) - // 13: account_compression_program (readonly) - // 14: system_program (readonly) - // 15: ctoken_program (readonly) - required for CPI + // 0: authority (PDA, readonly - program signs) + // 1: payer (signer, writable) + // 2: cmint (writable) + // 3: compressible_config (readonly) + // 4: rent_sponsor (writable) + // 5: state_tree (writable) + // 6: input_queue (writable) + // 7: output_queue (writable) + // 8: light_system_program (readonly) + // 9: cpi_authority_pda (readonly) + // 10: registered_program_pda (readonly) + // 11: account_compression_authority (readonly) + // 12: account_compression_program (readonly) + // 13: system_program (readonly) + // 14: light_token_program (readonly) - required for CPI let light_token_program_id = Pubkey::new_from_array(light_token_interface::LIGHT_TOKEN_PROGRAM_ID); let wrapper_accounts = vec![ - AccountMeta::new_readonly(mint_signer_pda, false), AccountMeta::new_readonly(pda_mint_authority, false), AccountMeta::new(payer.pubkey(), true), AccountMeta::new(mint_pda, false), diff --git a/sdk-tests/sdk-light-token-test/tests/test_decompress_cmint.rs b/sdk-tests/sdk-light-token-test/tests/test_decompress_cmint.rs index 73a21d6516..a07621a232 100644 --- a/sdk-tests/sdk-light-token-test/tests/test_decompress_cmint.rs +++ b/sdk-tests/sdk-light-token-test/tests/test_decompress_cmint.rs @@ -655,10 +655,25 @@ async fn test_decompress_cmint_cpi_invoke_signed() { ] .concat(); + // Account order matches process_decompress_cmint_invoke_signed: + // 0: authority (PDA, readonly - program signs) + // 1: payer (signer, writable) + // 2: cmint (writable) + // 3: compressible_config (readonly) + // 4: rent_sponsor (writable) + // 5: state_tree (writable) + // 6: input_queue (writable) + // 7: output_queue (writable) + // 8: light_system_program (readonly) + // 9: cpi_authority_pda (readonly) + // 10: registered_program_pda (readonly) + // 11: account_compression_authority (readonly) + // 12: account_compression_program (readonly) + // 13: system_program (readonly) + // 14: light_token_program (readonly) - required for CPI let light_token_program_id = Pubkey::new_from_array(light_token_interface::LIGHT_TOKEN_PROGRAM_ID); let wrapper_accounts = vec![ - AccountMeta::new_readonly(mint_signer_pda, false), AccountMeta::new_readonly(pda_mint_authority, false), AccountMeta::new(payer.pubkey(), true), AccountMeta::new(mint_pda, false),