From 402fde62725765c1398a78261ceb465209bcbdd1 Mon Sep 17 00:00:00 2001 From: ananas Date: Wed, 17 Dec 2025 21:45:34 +0000 Subject: [PATCH] chore: add missing compressible extension placeholder --- Cargo.lock | 4 +- Cargo.toml | 2 +- forester/src/compressible/compressor.rs | 4 +- forester/src/compressible/state.rs | 1 + .../ctoken-interface/src/constants.rs | 10 +-- .../src/state/ctoken/zero_copy.rs | 55 +++++++++------- .../src/state/extensions/extension_struct.rs | 41 ++++++++---- .../tests/ctoken/spl_compat.rs | 66 ++++++++++++------- .../tests/ctoken/compress_and_close.rs | 4 +- .../tests/ctoken/create_ata.rs | 2 +- .../tests/ctoken/functional.rs | 7 +- .../tests/ctoken/shared.rs | 6 +- .../registry-test/tests/compressible.rs | 9 +-- program-tests/utils/src/assert_claim.rs | 14 ++-- .../utils/src/assert_close_token_account.rs | 24 ++++--- .../utils/src/assert_create_token_account.rs | 33 ++++++---- .../utils/src/assert_ctoken_transfer.rs | 17 ++--- program-tests/utils/src/assert_mint_action.rs | 1 + program-tests/utils/src/assert_transfer2.rs | 2 +- .../compressed-token/program/src/claim.rs | 1 + .../src/close_token_account/processor.rs | 20 +++--- .../program/src/ctoken_transfer.rs | 1 + .../src/shared/initialize_ctoken_account.rs | 15 +++-- .../compression/ctoken/compress_and_close.rs | 2 +- .../ctoken/compress_or_decompress_ctokens.rs | 1 + .../program/tests/compress_and_close.rs | 16 +++-- .../compressed_token/v2/compress_and_close.rs | 15 +++-- sdk-libs/program-test/src/compressible.rs | 6 +- .../forester/compress_and_close_forester.rs | 6 +- .../src/instructions/transfer2.rs | 7 +- 30 files changed, 238 insertions(+), 154 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 72642469fc..2e157da1b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5097,7 +5097,7 @@ dependencies = [ [[package]] name = "pinocchio-token-interface" version = "0.0.0" -source = "git+https://github.com/Lightprotocol/token?rev=38d8634353e5eeb8c015d364df0eaa39f5c48b05#38d8634353e5eeb8c015d364df0eaa39f5c48b05" +source = "git+https://github.com/Lightprotocol/token?rev=fc39a485cf1b214e16a0f13fd6ddea0cd87aaa87#fc39a485cf1b214e16a0f13fd6ddea0cd87aaa87" dependencies = [ "pinocchio", "pinocchio-pubkey", @@ -5106,7 +5106,7 @@ dependencies = [ [[package]] name = "pinocchio-token-program" version = "0.1.0" -source = "git+https://github.com/Lightprotocol/token?rev=38d8634353e5eeb8c015d364df0eaa39f5c48b05#38d8634353e5eeb8c015d364df0eaa39f5c48b05" +source = "git+https://github.com/Lightprotocol/token?rev=fc39a485cf1b214e16a0f13fd6ddea0cd87aaa87#fc39a485cf1b214e16a0f13fd6ddea0cd87aaa87" dependencies = [ "pinocchio", "pinocchio-log", diff --git a/Cargo.toml b/Cargo.toml index ce6c9d3342..d562fcce98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -229,7 +229,7 @@ groth16-solana = { version = "0.2.0" } bytemuck = { version = "1.19.0" } arrayvec = "0.7" tinyvec = "1.10.0" -pinocchio-token-program = { git= "https://github.com/Lightprotocol/token", rev="38d8634353e5eeb8c015d364df0eaa39f5c48b05" } +pinocchio-token-program = { git= "https://github.com/Lightprotocol/token", rev="fc39a485cf1b214e16a0f13fd6ddea0cd87aaa87" } # Math and crypto num-bigint = "0.4.6" tabled = "0.20" diff --git a/forester/src/compressible/compressor.rs b/forester/src/compressible/compressor.rs index 7801158485..fe9bbafec6 100644 --- a/forester/src/compressible/compressor.rs +++ b/forester/src/compressible/compressor.rs @@ -140,7 +140,7 @@ impl Compressor { .ok_or_else(|| anyhow::anyhow!("Account missing compressible extension"))?; // Determine owner based on compress_to_pubkey flag - let compressed_token_owner = if compressible_ext.compress_to_pubkey != 0 { + let compressed_token_owner = if compressible_ext.info.compress_to_pubkey != 0 { account_state.pubkey // Use account pubkey for PDAs } else { Pubkey::new_from_array(account_state.account.owner.to_bytes()) // Use original owner @@ -149,7 +149,7 @@ impl Compressor { let owner_index = packed_accounts.insert_or_get(compressed_token_owner); // Extract rent_sponsor from extension - let rent_sponsor = Pubkey::new_from_array(compressible_ext.rent_sponsor); + let rent_sponsor = Pubkey::new_from_array(compressible_ext.info.rent_sponsor); let rent_sponsor_index = packed_accounts.insert_or_get(rent_sponsor); indices_vec.push(CompressAndCloseIndices { diff --git a/forester/src/compressible/state.rs b/forester/src/compressible/state.rs index accda1359c..021b8cd10f 100644 --- a/forester/src/compressible/state.rs +++ b/forester/src/compressible/state.rs @@ -31,6 +31,7 @@ fn calculate_compressible_slot(account: &CToken, lamports: u64) -> Result { // Calculate last funded epoch let last_funded_epoch = compressible_ext + .info .get_last_funded_epoch( COMPRESSIBLE_TOKEN_ACCOUNT_SIZE, lamports, diff --git a/program-libs/ctoken-interface/src/constants.rs b/program-libs/ctoken-interface/src/constants.rs index cd6d438630..a024d0892a 100644 --- a/program-libs/ctoken-interface/src/constants.rs +++ b/program-libs/ctoken-interface/src/constants.rs @@ -1,6 +1,6 @@ use light_macros::pubkey_array; -use crate::state::CompressionInfo; +use crate::state::extensions::CompressibleExtension; pub const CPI_AUTHORITY: [u8; 32] = pubkey_array!("GXtd2izAiMJPwMEjfgTRH3d7k9mjn4Jq3JrWFv9gySYy"); pub const CTOKEN_PROGRAM_ID: [u8; 32] = @@ -13,13 +13,15 @@ pub const BASE_TOKEN_ACCOUNT_SIZE: u64 = 165; /// Extension metadata overhead: AccountType (1) + Option discriminator (1) + Vec length (4) + Extension enum variant (1) pub const EXTENSION_METADATA: u64 = 7; -/// Size of a token account with compressible extension 260 bytes. +/// Size of a token account with compressible extension 261 bytes. +/// CompressibleExtension = compression_only (1 byte) + CompressionInfo (88 bytes) = 89 bytes pub const COMPRESSIBLE_TOKEN_ACCOUNT_SIZE: u64 = - BASE_TOKEN_ACCOUNT_SIZE + CompressionInfo::LEN as u64 + EXTENSION_METADATA; + BASE_TOKEN_ACCOUNT_SIZE + CompressibleExtension::LEN as u64 + EXTENSION_METADATA; /// Rent exemption threshold for compressible token accounts (in lamports) /// This value determines when an account has sufficient rent to be considered not compressible -pub const COMPRESSIBLE_TOKEN_RENT_EXEMPTION: u64 = 2700480; +/// Calculation: (account_size + 128) * 3480 * 2 = (261 + 128) * 6960 = 2707440 +pub const COMPRESSIBLE_TOKEN_RENT_EXEMPTION: u64 = 2707440; /// Size of a Token-2022 mint account pub const MINT_ACCOUNT_SIZE: u64 = 82; diff --git a/program-libs/ctoken-interface/src/state/ctoken/zero_copy.rs b/program-libs/ctoken-interface/src/state/ctoken/zero_copy.rs index b416a2651e..a8f4d50016 100644 --- a/program-libs/ctoken-interface/src/state/ctoken/zero_copy.rs +++ b/program-libs/ctoken-interface/src/state/ctoken/zero_copy.rs @@ -10,8 +10,8 @@ use spl_pod::solana_msg::msg; use crate::{ state::{ - CToken, CompressionInfoConfig, ExtensionStruct, ExtensionStructConfig, ZExtensionStruct, - ZExtensionStructMut, + CToken, CompressibleExtensionConfig, CompressionInfoConfig, ExtensionStruct, + ExtensionStructConfig, ZExtensionStruct, ZExtensionStructMut, }, AnchorDeserialize, AnchorSerialize, }; @@ -290,67 +290,72 @@ impl PartialEq for ZCToken<'_> { crate::state::extensions::ExtensionStruct::Compressible(regular_comp), ) => { // Compare config_account_version - if zc_comp.config_account_version != regular_comp.config_account_version + if zc_comp.info.config_account_version + != regular_comp.info.config_account_version { return false; } // Compare compress_to_pubkey - if zc_comp.compress_to_pubkey != regular_comp.compress_to_pubkey { + if zc_comp.info.compress_to_pubkey + != regular_comp.info.compress_to_pubkey + { return false; } // Compare account_version - if zc_comp.account_version != regular_comp.account_version { + if zc_comp.info.account_version != regular_comp.info.account_version { return false; } // Compare last_claimed_slot - if u64::from(zc_comp.last_claimed_slot) - != regular_comp.last_claimed_slot + if u64::from(zc_comp.info.last_claimed_slot) + != regular_comp.info.last_claimed_slot { return false; } // Compare rent_config fields - if u16::from(zc_comp.rent_config.base_rent) - != regular_comp.rent_config.base_rent + if u16::from(zc_comp.info.rent_config.base_rent) + != regular_comp.info.rent_config.base_rent { return false; } - if u16::from(zc_comp.rent_config.compression_cost) - != regular_comp.rent_config.compression_cost + if u16::from(zc_comp.info.rent_config.compression_cost) + != regular_comp.info.rent_config.compression_cost { return false; } - if zc_comp.rent_config.lamports_per_byte_per_epoch - != regular_comp.rent_config.lamports_per_byte_per_epoch + if zc_comp.info.rent_config.lamports_per_byte_per_epoch + != regular_comp.info.rent_config.lamports_per_byte_per_epoch { return false; } - if zc_comp.rent_config.max_funded_epochs - != regular_comp.rent_config.max_funded_epochs + if zc_comp.info.rent_config.max_funded_epochs + != regular_comp.info.rent_config.max_funded_epochs { return false; } - if u16::from(zc_comp.rent_config.max_top_up) - != regular_comp.rent_config.max_top_up + if u16::from(zc_comp.info.rent_config.max_top_up) + != regular_comp.info.rent_config.max_top_up { return false; } // Compare compression_authority ([u8; 32]) - if zc_comp.compression_authority != regular_comp.compression_authority { + if zc_comp.info.compression_authority + != regular_comp.info.compression_authority + { return false; } // Compare rent_sponsor ([u8; 32]) - if zc_comp.rent_sponsor != regular_comp.rent_sponsor { + if zc_comp.info.rent_sponsor != regular_comp.info.rent_sponsor { return false; } // Compare lamports_per_write (u32) - if u32::from(zc_comp.lamports_per_write) - != regular_comp.lamports_per_write + if u32::from(zc_comp.info.lamports_per_write) + != regular_comp.info.lamports_per_write { return false; } @@ -647,9 +652,11 @@ impl CompressedTokenConfig { delegate, is_native, close_authority, - extensions: vec![ExtensionStructConfig::Compressible(CompressionInfoConfig { - rent_config: (), - })], + extensions: vec![ExtensionStructConfig::Compressible( + CompressibleExtensionConfig { + info: CompressionInfoConfig { rent_config: () }, + }, + )], } } } diff --git a/program-libs/ctoken-interface/src/state/extensions/extension_struct.rs b/program-libs/ctoken-interface/src/state/extensions/extension_struct.rs index 8a94a29323..776874959d 100644 --- a/program-libs/ctoken-interface/src/state/extensions/extension_struct.rs +++ b/program-libs/ctoken-interface/src/state/extensions/extension_struct.rs @@ -1,11 +1,9 @@ -use light_zero_copy::ZeroCopy; +use aligned_sized::aligned_sized; +use light_zero_copy::{ZeroCopy, ZeroCopyMut}; use spl_pod::solana_msg::msg; use crate::{ - state::{ - extensions::{CompressionInfo, TokenMetadata, TokenMetadataConfig, ZTokenMetadataMut}, - CompressionInfoConfig, - }, + state::extensions::{CompressionInfo, TokenMetadata, TokenMetadataConfig, ZTokenMetadataMut}, AnchorDeserialize, AnchorSerialize, }; @@ -48,7 +46,26 @@ pub enum ExtensionStruct { Placeholder30, Placeholder31, /// Account contains compressible timing data and rent authority - Compressible(CompressionInfo), + Compressible(CompressibleExtension), +} + +#[derive( + Debug, + ZeroCopy, + ZeroCopyMut, + Clone, + Copy, + PartialEq, + Hash, + Eq, + AnchorSerialize, + AnchorDeserialize, +)] +#[repr(C)] +#[aligned_sized] +pub struct CompressibleExtension { + pub compression_only: bool, + pub info: CompressionInfo, } #[derive(Debug)] @@ -89,7 +106,9 @@ pub enum ZExtensionStructMut<'a> { Placeholder30, Placeholder31, /// Account contains compressible timing data and rent authority - Compressible(>::ZeroCopyAtMut), + Compressible( + >::ZeroCopyAtMut, + ), } impl<'a> light_zero_copy::traits::ZeroCopyAtMut<'a> for ExtensionStruct { @@ -120,7 +139,7 @@ impl<'a> light_zero_copy::traits::ZeroCopyAtMut<'a> for ExtensionStruct { 32 => { // Compressible variant (index 32 to avoid Token-2022 overlap) let (compressible_ext, remaining_bytes) = - CompressionInfo::zero_copy_at_mut(remaining_data)?; + CompressibleExtension::zero_copy_at_mut(remaining_data)?; Ok(( ZExtensionStructMut::Compressible(compressible_ext), remaining_bytes, @@ -145,7 +164,7 @@ impl<'a> light_zero_copy::ZeroCopyNew<'a> for ExtensionStruct { } ExtensionStructConfig::Compressible(config) => { // 1 byte for discriminant + CompressionInfo size - 1 + CompressionInfo::byte_len(config)? + 1 + CompressibleExtension::byte_len(config)? } _ => { msg!("Invalid extension type returning"); @@ -187,7 +206,7 @@ impl<'a> light_zero_copy::ZeroCopyNew<'a> for ExtensionStruct { bytes[0] = 32u8; let (compressible_ext, remaining_bytes) = - CompressionInfo::new_zero_copy(&mut bytes[1..], config)?; + CompressibleExtension::new_zero_copy(&mut bytes[1..], config)?; Ok(( ZExtensionStructMut::Compressible(compressible_ext), remaining_bytes, @@ -235,5 +254,5 @@ pub enum ExtensionStructConfig { Placeholder29, Placeholder30, Placeholder31, - Compressible(CompressionInfoConfig), + Compressible(CompressibleExtensionConfig), } diff --git a/program-libs/ctoken-interface/tests/ctoken/spl_compat.rs b/program-libs/ctoken-interface/tests/ctoken/spl_compat.rs index 762838b426..2f621ad2fb 100644 --- a/program-libs/ctoken-interface/tests/ctoken/spl_compat.rs +++ b/program-libs/ctoken-interface/tests/ctoken/spl_compat.rs @@ -8,7 +8,7 @@ use light_compressed_account::Pubkey; use light_ctoken_interface::state::{ ctoken::{CToken, CompressedTokenConfig, ZCToken}, - CompressionInfoConfig, ExtensionStructConfig, + CompressibleExtensionConfig, CompressionInfoConfig, ExtensionStructConfig, }; use light_zero_copy::traits::{ZeroCopyAt, ZeroCopyAtMut, ZeroCopyNew}; use rand::Rng; @@ -390,9 +390,11 @@ fn test_compressed_token_with_compressible_extension() { delegate: false, is_native: false, close_authority: false, - extensions: vec![ExtensionStructConfig::Compressible(CompressionInfoConfig { - rent_config: (), - })], + extensions: vec![ExtensionStructConfig::Compressible( + CompressibleExtensionConfig { + info: CompressionInfoConfig { rent_config: () }, + }, + )], }; // Calculate required buffer size (165 base + 1 AccountType + 1 Option + extension data) @@ -451,9 +453,11 @@ fn test_account_type_compatibility_with_spl_parsing() { delegate: false, is_native: false, close_authority: false, - extensions: vec![ExtensionStructConfig::Compressible(CompressionInfoConfig { - rent_config: (), - })], + extensions: vec![ExtensionStructConfig::Compressible( + CompressibleExtensionConfig { + info: CompressionInfoConfig { rent_config: () }, + }, + )], }; let mut buffer = vec![0u8; CToken::byte_len(&config).unwrap()]; @@ -491,16 +495,19 @@ fn test_account_type_compatibility_with_spl_parsing() { fn test_compressible_extension_partial_eq() { use light_compressible::{compression_info::CompressionInfo, rent::RentConfig}; use light_ctoken_interface::state::{ - ctoken::AccountState as CtokenAccountState, extensions::ExtensionStruct, + ctoken::AccountState as CtokenAccountState, + extensions::{CompressibleExtension, ExtensionStruct}, }; let config = CompressedTokenConfig { delegate: false, is_native: false, close_authority: false, - extensions: vec![ExtensionStructConfig::Compressible(CompressionInfoConfig { - rent_config: (), - })], + extensions: vec![ExtensionStructConfig::Compressible( + CompressibleExtensionConfig { + info: CompressionInfoConfig { rent_config: () }, + }, + )], }; let mut buffer = vec![0u8; CToken::byte_len(&config).unwrap()]; @@ -514,13 +521,13 @@ fn test_compressible_extension_partial_eq() { ref mut comp, ) = ext { - comp.config_account_version = 1.into(); - comp.compress_to_pubkey = 1; - comp.account_version = 2; - comp.lamports_per_write = 100.into(); - comp.compression_authority = [1u8; 32]; - comp.rent_sponsor = [2u8; 32]; - comp.last_claimed_slot = 1000.into(); + comp.info.config_account_version = 1.into(); + comp.info.compress_to_pubkey = 1; + comp.info.account_version = 2; + comp.info.lamports_per_write = 100.into(); + comp.info.compression_authority = [1u8; 32]; + comp.info.rent_sponsor = [2u8; 32]; + comp.info.last_claimed_slot = 1000.into(); } } } @@ -553,7 +560,10 @@ fn test_compressible_extension_partial_eq() { is_native: None, delegated_amount: 0, close_authority: None, - extensions: Some(vec![ExtensionStruct::Compressible(compression_info)]), + extensions: Some(vec![ExtensionStruct::Compressible(CompressibleExtension { + compression_only: false, + info: compression_info, + })]), }; // Parse zero-copy view @@ -565,9 +575,12 @@ fn test_compressible_extension_partial_eq() { // Test compress_to_pubkey mismatch let ctoken_diff_compress = CToken { - extensions: Some(vec![ExtensionStruct::Compressible(CompressionInfo { - compress_to_pubkey: 0, - ..compression_info + extensions: Some(vec![ExtensionStruct::Compressible(CompressibleExtension { + compression_only: false, + info: CompressionInfo { + compress_to_pubkey: 0, + ..compression_info + }, })]), ..ctoken.clone() }; @@ -576,9 +589,12 @@ fn test_compressible_extension_partial_eq() { // Test account_version mismatch let ctoken_diff_version = CToken { - extensions: Some(vec![ExtensionStruct::Compressible(CompressionInfo { - account_version: 0, - ..compression_info + extensions: Some(vec![ExtensionStruct::Compressible(CompressibleExtension { + compression_only: false, + info: CompressionInfo { + account_version: 0, + ..compression_info + }, })]), ..ctoken.clone() }; diff --git a/program-tests/compressed-token-test/tests/ctoken/compress_and_close.rs b/program-tests/compressed-token-test/tests/ctoken/compress_and_close.rs index de57da15a5..a5cdf3a2ed 100644 --- a/program-tests/compressed-token-test/tests/ctoken/compress_and_close.rs +++ b/program-tests/compressed-token-test/tests/ctoken/compress_and_close.rs @@ -441,7 +441,7 @@ async fn test_compress_and_close_compress_to_pubkey() { if let Some(extensions) = ctoken.extensions.as_mut() { for ext in extensions.iter_mut() { if let ZExtensionStructMut::Compressible(ref mut comp) = ext { - comp.compress_to_pubkey = 1; + comp.info.compress_to_pubkey = 1; break; } } @@ -778,7 +778,7 @@ async fn test_compress_and_close_output_validation_errors() { if let Some(extensions) = ctoken.extensions.as_mut() { for ext in extensions.iter_mut() { if let ZExtensionStructMut::Compressible(ref mut comp) = ext { - comp.compress_to_pubkey = 1; + comp.info.compress_to_pubkey = 1; break; } } diff --git a/program-tests/compressed-token-test/tests/ctoken/create_ata.rs b/program-tests/compressed-token-test/tests/ctoken/create_ata.rs index 37651bcd3f..9d232c7841 100644 --- a/program-tests/compressed-token-test/tests/ctoken/create_ata.rs +++ b/program-tests/compressed-token-test/tests/ctoken/create_ata.rs @@ -168,7 +168,7 @@ async fn test_create_ata_idempotent() { // Verify the account still has the same properties (unchanged by second creation) let account = context.rpc.get_account(ata_pubkey).await.unwrap().unwrap(); - // Should still be 260 bytes (compressible) + // Should still be compressible size (COMPRESSIBLE_TOKEN_ACCOUNT_SIZE bytes) assert_eq!( account.data.len(), light_ctoken_interface::COMPRESSIBLE_TOKEN_ACCOUNT_SIZE as usize, diff --git a/program-tests/compressed-token-test/tests/ctoken/functional.rs b/program-tests/compressed-token-test/tests/ctoken/functional.rs index af4d0d253c..40c6d5bb8f 100644 --- a/program-tests/compressed-token-test/tests/ctoken/functional.rs +++ b/program-tests/compressed-token-test/tests/ctoken/functional.rs @@ -215,11 +215,12 @@ async fn test_compressible_account_with_compression_authority_lifecycle() { // Calculate transaction fee from the transaction result let tx_fee = 10_000; // Standard transaction fee - // With 3 prepaid epochs: compression_cost (11000) + 3 * rent_per_epoch (388) = 12164 + // With 3 prepaid epochs: compression_cost (11000) + 3 * rent_per_epoch (389) = 12167 + // rent_per_epoch = 261 bytes * 1 lamport/byte/epoch + base_rent (128) = 389 assert_eq!( payer_balance_before - payer_balance_after, - 12_164 + tx_fee, - "Payer should have paid 12,164 lamports for additional rent (3 epochs) plus {} tx fee", + 12_167 + tx_fee, + "Payer should have paid 12,167 lamports for additional rent (3 epochs) plus {} tx fee", tx_fee ); diff --git a/program-tests/compressed-token-test/tests/ctoken/shared.rs b/program-tests/compressed-token-test/tests/ctoken/shared.rs index 0847e11fac..67af58b715 100644 --- a/program-tests/compressed-token-test/tests/ctoken/shared.rs +++ b/program-tests/compressed-token-test/tests/ctoken/shared.rs @@ -312,7 +312,9 @@ pub async fn close_and_assert_token_account( extensions .iter() .find_map(|ext| match ext { - ZExtensionStruct::Compressible(comp) => Some(Pubkey::from(comp.rent_sponsor)), + ZExtensionStruct::Compressible(comp) => { + Some(Pubkey::from(comp.info.rent_sponsor)) + } _ => None, }) .unwrap() @@ -728,7 +730,7 @@ pub async fn compress_and_close_forester_with_invalid_output( }) .unwrap(); - let rent_sponsor = Pubkey::from(compressible_ext.rent_sponsor); + let rent_sponsor = Pubkey::from(compressible_ext.info.rent_sponsor); // Get output queue for compression let output_queue = context diff --git a/program-tests/registry-test/tests/compressible.rs b/program-tests/registry-test/tests/compressible.rs index 8bb744ebf8..87cd16fee7 100644 --- a/program-tests/registry-test/tests/compressible.rs +++ b/program-tests/registry-test/tests/compressible.rs @@ -1143,10 +1143,10 @@ async fn assert_not_compressible( num_bytes: account.data.len() as u64, current_slot, current_lamports: account.lamports, - last_claimed_slot: compressible_ext.last_claimed_slot, + last_claimed_slot: compressible_ext.info.last_claimed_slot, }; let is_compressible = state.is_compressible( - &compressible_ext.rent_config, + &compressible_ext.info.rent_config, light_ctoken_interface::COMPRESSIBLE_TOKEN_RENT_EXEMPTION, ); @@ -1159,6 +1159,7 @@ async fn assert_not_compressible( // Also verify last_funded_epoch is ahead of current let last_funded_epoch = compressible_ext + .info .get_last_funded_epoch( account.data.len() as u64, account.lamports, @@ -1270,7 +1271,7 @@ async fn test_compressible_account_infinite_funding() -> Result<(), RpcError> { .and_then(|exts| { exts.iter().find_map(|ext| { if let ExtensionStruct::Compressible(comp) = ext { - Some(comp.rent_config) + Some(comp.info.rent_config) } else { None } @@ -1299,7 +1300,7 @@ async fn test_compressible_account_infinite_funding() -> Result<(), RpcError> { if let Some(extensions) = ctoken.extensions.as_ref() { for ext in extensions.iter() { if let ExtensionStruct::Compressible(comp) = ext { - return Ok(comp.last_claimed_slot); + return Ok(comp.info.last_claimed_slot); } } } diff --git a/program-tests/utils/src/assert_claim.rs b/program-tests/utils/src/assert_claim.rs index b2d009c928..1461b01f38 100644 --- a/program-tests/utils/src/assert_claim.rs +++ b/program-tests/utils/src/assert_claim.rs @@ -42,17 +42,17 @@ pub async fn assert_claim( if let Some(extensions) = pre_compressed_token.extensions.as_mut() { for extension in extensions { if let ZExtensionStructMut::Compressible(compressible_ext) = extension { - pre_last_claimed_slot = u64::from(compressible_ext.last_claimed_slot); + pre_last_claimed_slot = u64::from(compressible_ext.info.last_claimed_slot); // Check if compression_authority is set (non-zero) pre_compression_authority = - if compressible_ext.compression_authority != [0u8; 32] { - Some(Pubkey::from(compressible_ext.compression_authority)) + if compressible_ext.info.compression_authority != [0u8; 32] { + Some(Pubkey::from(compressible_ext.info.compression_authority)) } else { None }; // Check if rent_sponsor is set (non-zero) - pre_rent_sponsor = if compressible_ext.rent_sponsor != [0u8; 32] { - Some(Pubkey::from(compressible_ext.rent_sponsor)) + pre_rent_sponsor = if compressible_ext.info.rent_sponsor != [0u8; 32] { + Some(Pubkey::from(compressible_ext.info.rent_sponsor)) } else { None }; @@ -63,7 +63,7 @@ pub async fn assert_claim( ) .await .unwrap(); - let lamports_result = compressible_ext.claim( + let lamports_result = compressible_ext.info.claim( COMPRESSIBLE_TOKEN_ACCOUNT_SIZE, current_slot, pre_token_account.lamports, @@ -110,7 +110,7 @@ pub async fn assert_claim( if let Some(extensions) = post_compressed_token.extensions.as_ref() { for extension in extensions { if let ZExtensionStruct::Compressible(compressible_ext) = extension { - post_last_claimed_slot = u64::from(compressible_ext.last_claimed_slot); + post_last_claimed_slot = u64::from(compressible_ext.info.last_claimed_slot); println!("post_last_claimed_slot {}", post_last_claimed_slot); break; diff --git a/program-tests/utils/src/assert_close_token_account.rs b/program-tests/utils/src/assert_close_token_account.rs index 6b62849fdb..7bd36b5155 100644 --- a/program-tests/utils/src/assert_close_token_account.rs +++ b/program-tests/utils/src/assert_close_token_account.rs @@ -161,17 +161,17 @@ async fn assert_compressible_extension( // Verify compressible extension fields are valid let current_slot = rpc.get_slot().await.expect("Failed to get current slot"); assert!( - u64::from(compressible_extension.last_claimed_slot) <= current_slot, + u64::from(compressible_extension.info.last_claimed_slot) <= current_slot, "Last claimed slot ({}) should not be greater than current slot ({})", - u64::from(compressible_extension.last_claimed_slot), + u64::from(compressible_extension.info.last_claimed_slot), current_slot ); // Verify config_account_version is initialized assert!( - compressible_extension.config_account_version == 1, + compressible_extension.info.config_account_version == 1, "Config account version should be 1 (initialized), got {}", - compressible_extension.config_account_version + compressible_extension.info.config_account_version ); // Calculate expected lamport distribution using the same function as the program @@ -186,24 +186,28 @@ async fn assert_compressible_extension( num_bytes: account_size, current_slot, current_lamports: account_lamports_before_close, - last_claimed_slot: u64::from(compressible_extension.last_claimed_slot), + last_claimed_slot: u64::from(compressible_extension.info.last_claimed_slot), }; let distribution = - state.calculate_close_distribution(&compressible_extension.rent_config, base_lamports); + state.calculate_close_distribution(&compressible_extension.info.rent_config, base_lamports); let (mut lamports_to_rent_sponsor, mut lamports_to_destination) = (distribution.to_rent_sponsor, distribution.to_user); - let compression_cost: u64 = compressible_extension.rent_config.compression_cost.into(); + let compression_cost: u64 = compressible_extension + .info + .rent_config + .compression_cost + .into(); // Get the rent recipient from the extension - let rent_sponsor = Pubkey::from(compressible_extension.rent_sponsor); + let rent_sponsor = Pubkey::from(compressible_extension.info.rent_sponsor); // Check if rent authority is the signer // Check if compression_authority is set (non-zero) let is_compression_authority_signer = - if compressible_extension.compression_authority != [0u8; 32] { - authority_pubkey == Pubkey::from(compressible_extension.compression_authority) + if compressible_extension.info.compression_authority != [0u8; 32] { + authority_pubkey == Pubkey::from(compressible_extension.info.compression_authority) } else { false }; diff --git a/program-tests/utils/src/assert_create_token_account.rs b/program-tests/utils/src/assert_create_token_account.rs index adaa4adac9..c4bda51a12 100644 --- a/program-tests/utils/src/assert_create_token_account.rs +++ b/program-tests/utils/src/assert_create_token_account.rs @@ -2,7 +2,11 @@ use anchor_spl::token_2022::spl_token_2022; use light_client::rpc::Rpc; use light_compressible::rent::RentConfig; use light_ctoken_interface::{ - state::{ctoken::CToken, extensions::CompressionInfo, AccountState}, + state::{ + ctoken::CToken, + extensions::{CompressibleExtension, CompressionInfo}, + AccountState, + }, BASE_TOKEN_ACCOUNT_SIZE, COMPRESSIBLE_TOKEN_ACCOUNT_SIZE, }; use light_ctoken_sdk::ctoken::derive_ctoken_ata; @@ -91,17 +95,22 @@ pub async fn assert_create_token_account_internal( close_authority: None, extensions: Some(vec![ light_ctoken_interface::state::extensions::ExtensionStruct::Compressible( - CompressionInfo { - config_account_version: 1, - last_claimed_slot: current_slot, - rent_config: RentConfig::default(), - lamports_per_write: compressible_info.lamports_per_write.unwrap_or(0), - compression_authority: compressible_info - .compression_authority - .to_bytes(), - rent_sponsor: compressible_info.rent_sponsor.to_bytes(), - compress_to_pubkey: compressible_info.compress_to_pubkey as u8, - account_version: compressible_info.account_version as u8, + CompressibleExtension { + compression_only: false, + info: CompressionInfo { + config_account_version: 1, + last_claimed_slot: current_slot, + rent_config: RentConfig::default(), + lamports_per_write: compressible_info + .lamports_per_write + .unwrap_or(0), + compression_authority: compressible_info + .compression_authority + .to_bytes(), + rent_sponsor: compressible_info.rent_sponsor.to_bytes(), + compress_to_pubkey: compressible_info.compress_to_pubkey as u8, + account_version: compressible_info.account_version as u8, + }, }, ), ]), diff --git a/program-tests/utils/src/assert_ctoken_transfer.rs b/program-tests/utils/src/assert_ctoken_transfer.rs index 7b1fa33570..d0eb050681 100644 --- a/program-tests/utils/src/assert_ctoken_transfer.rs +++ b/program-tests/utils/src/assert_ctoken_transfer.rs @@ -77,33 +77,34 @@ pub async fn assert_compressible_for_account( }); assert_eq!( - u64::from(compressible_after.last_claimed_slot), - u64::from(compressible_before.last_claimed_slot), + u64::from(compressible_after.info.last_claimed_slot), + u64::from(compressible_before.info.last_claimed_slot), "{} last_claimed_slot should be different from current slot before transfer", name ); assert_eq!( - compressible_before.compression_authority, - compressible_after.compression_authority, + compressible_before.info.compression_authority, + compressible_after.info.compression_authority, "{} compression_authority should not change", name ); assert_eq!( - compressible_before.rent_sponsor, compressible_after.rent_sponsor, + compressible_before.info.rent_sponsor, compressible_after.info.rent_sponsor, "{} rent_sponsor should not change", name ); assert_eq!( - compressible_before.config_account_version, - compressible_after.config_account_version, + compressible_before.info.config_account_version, + compressible_after.info.config_account_version, "{} config_account_version should not change", name ); let current_slot = rpc.get_slot().await.unwrap(); let top_up = compressible_before + .info .calculate_top_up_lamports( - 260, + light_ctoken_interface::COMPRESSIBLE_TOKEN_ACCOUNT_SIZE, current_slot, lamports_before, light_ctoken_interface::COMPRESSIBLE_TOKEN_RENT_EXEMPTION, diff --git a/program-tests/utils/src/assert_mint_action.rs b/program-tests/utils/src/assert_mint_action.rs index 03051855b9..372f526f24 100644 --- a/program-tests/utils/src/assert_mint_action.rs +++ b/program-tests/utils/src/assert_mint_action.rs @@ -182,6 +182,7 @@ pub async fn assert_mint_action( let account_size = pre_account.data.len() as u64; let expected_top_up = compressible + .info .calculate_top_up_lamports( account_size, current_slot, diff --git a/program-tests/utils/src/assert_transfer2.rs b/program-tests/utils/src/assert_transfer2.rs index e276d9abdc..2e1d4456eb 100644 --- a/program-tests/utils/src/assert_transfer2.rs +++ b/program-tests/utils/src/assert_transfer2.rs @@ -399,7 +399,7 @@ pub async fn assert_transfer2_with_delegate( .iter() .find_map(|ext| match ext { ZExtensionStruct::Compressible(comp) => { - Some(comp.compress_to_pubkey == 1) + Some(comp.info.compress_to_pubkey == 1) } _ => None, }) diff --git a/programs/compressed-token/program/src/claim.rs b/programs/compressed-token/program/src/claim.rs index e247bf0dc6..0be65a02d4 100644 --- a/programs/compressed-token/program/src/claim.rs +++ b/programs/compressed-token/program/src/claim.rs @@ -111,6 +111,7 @@ fn validate_and_claim( for extension in extensions { if let ZExtensionStructMut::Compressible(compressible_ext) = extension { return compressible_ext + .info .claim_and_update(ClaimAndUpdate { compression_authority: accounts.compression_authority.key(), rent_sponsor: accounts.rent_sponsor.key(), diff --git a/programs/compressed-token/program/src/close_token_account/processor.rs b/programs/compressed-token/program/src/close_token_account/processor.rs index 0efdb4d2b5..511ad7715d 100644 --- a/programs/compressed-token/program/src/close_token_account/processor.rs +++ b/programs/compressed-token/program/src/close_token_account/processor.rs @@ -83,14 +83,14 @@ fn validate_token_account( let rent_sponsor = accounts .rent_sponsor .ok_or(ProgramError::NotEnoughAccountKeys)?; - if compressible_ext.rent_sponsor != *rent_sponsor.key() { + if compressible_ext.info.rent_sponsor != *rent_sponsor.key() { msg!("rent recipient mismatch"); return Err(ProgramError::InvalidAccountData); } if COMPRESS_AND_CLOSE { // For CompressAndClose: ONLY compression_authority can compress and close - if compressible_ext.compression_authority != *accounts.authority.key() { + if compressible_ext.info.compression_authority != *accounts.authority.key() { msg!("compress and close requires compression authority"); return Err(ProgramError::InvalidAccountData); } @@ -103,6 +103,7 @@ fn validate_token_account( #[cfg(target_os = "solana")] { let is_compressible = compressible_ext + .info .is_compressible( accounts.token_account.data_len() as u64, current_slot, @@ -116,7 +117,7 @@ fn validate_token_account( } } - return Ok(compressible_ext.compress_to_pubkey()); + return Ok(compressible_ext.info.compress_to_pubkey()); } // For regular close (!COMPRESS_AND_CLOSE): fall through to owner check } @@ -185,7 +186,8 @@ pub fn distribute_lamports(accounts: &CloseTokenAccountAccounts<'_>) -> Result<( .slot; #[cfg(not(target_os = "solana"))] let current_slot = 0; - let compression_cost: u64 = compressible_ext.rent_config.compression_cost.into(); + let compression_cost: u64 = + compressible_ext.info.rent_config.compression_cost.into(); let (mut lamports_to_rent_sponsor, mut lamports_to_destination) = { let base_lamports = @@ -196,11 +198,13 @@ pub fn distribute_lamports(accounts: &CloseTokenAccountAccounts<'_>) -> Result<( num_bytes: accounts.token_account.data_len() as u64, current_slot, current_lamports: token_account_lamports, - last_claimed_slot: compressible_ext.last_claimed_slot.into(), + last_claimed_slot: compressible_ext.info.last_claimed_slot.into(), }; - let distribution = state - .calculate_close_distribution(&compressible_ext.rent_config, base_lamports); + let distribution = state.calculate_close_distribution( + &compressible_ext.info.rent_config, + base_lamports, + ); (distribution.to_rent_sponsor, distribution.to_user) }; @@ -208,7 +212,7 @@ pub fn distribute_lamports(accounts: &CloseTokenAccountAccounts<'_>) -> Result<( .rent_sponsor .ok_or(ProgramError::NotEnoughAccountKeys)?; - if accounts.authority.key() == &compressible_ext.compression_authority { + if accounts.authority.key() == &compressible_ext.info.compression_authority { // When compressing via compression_authority: // Extract compression incentive from rent_sponsor portion to give to forester // The compression incentive is included in lamports_to_rent_sponsor diff --git a/programs/compressed-token/program/src/ctoken_transfer.rs b/programs/compressed-token/program/src/ctoken_transfer.rs index a02b76a6b4..7182a5f049 100644 --- a/programs/compressed-token/program/src/ctoken_transfer.rs +++ b/programs/compressed-token/program/src/ctoken_transfer.rs @@ -101,6 +101,7 @@ fn calculate_and_execute_top_up_transfers( } transfer.amount = compressible_extension + .info .calculate_top_up_lamports( transfer.account.data_len() as u64, current_slot, diff --git a/programs/compressed-token/program/src/shared/initialize_ctoken_account.rs b/programs/compressed-token/program/src/shared/initialize_ctoken_account.rs index 07eb43256b..5b25dce1af 100644 --- a/programs/compressed-token/program/src/shared/initialize_ctoken_account.rs +++ b/programs/compressed-token/program/src/shared/initialize_ctoken_account.rs @@ -3,7 +3,7 @@ use light_account_checks::AccountInfoTrait; use light_compressible::{compression_info::ZCompressionInfoMut, config::CompressibleConfig}; use light_ctoken_interface::{ instructions::extensions::compressible::CompressibleExtensionInstructionData, - state::CompressionInfo, CTokenError, COMPRESSIBLE_TOKEN_ACCOUNT_SIZE, + state::extensions::CompressibleExtension, CTokenError, COMPRESSIBLE_TOKEN_ACCOUNT_SIZE, }; use light_program_profiler::profile; use light_zero_copy::traits::ZeroCopyAtMut; @@ -86,18 +86,21 @@ pub fn initialize_ctoken_account( // Byte 6: Compressible enum discriminator = 32 (avoids Token-2022 overlap) extension_bytes[6] = 32; - // Create zero-copy mutable reference to CompressionInfo - let (mut compressible_extension, _) = CompressionInfo::zero_copy_at_mut(compressible_data) - .map_err(|e| { + // Create zero-copy mutable reference to CompressibleExtension + let (mut compressible_extension, _) = + CompressibleExtension::zero_copy_at_mut(compressible_data).map_err(|e| { msg!( - "Failed to create CompressionInfo zero-copy reference: {:?}", + "Failed to create CompressibleExtension zero-copy reference: {:?}", e ); ProgramError::InvalidAccountData })?; + // Set compression_only field (false = 0 by default, accounts are compressible) + compressible_extension.compression_only = 0; + configure_compressible_extension( - &mut compressible_extension, + &mut compressible_extension.info, compressible_config, compressible_config_account, custom_rent_payer, diff --git a/programs/compressed-token/program/src/transfer2/compression/ctoken/compress_and_close.rs b/programs/compressed-token/program/src/transfer2/compression/ctoken/compress_and_close.rs index bddf2d0ec7..cf24372afb 100644 --- a/programs/compressed-token/program/src/transfer2/compression/ctoken/compress_and_close.rs +++ b/programs/compressed-token/program/src/transfer2/compression/ctoken/compress_and_close.rs @@ -166,7 +166,7 @@ fn validate_compressed_token_account( .as_ref() .and_then(|ext| { if let Some(ZExtensionStructMut::Compressible(ext)) = ext.first() { - Some(ext.account_version) + Some(ext.info.account_version) } else { None } diff --git a/programs/compressed-token/program/src/transfer2/compression/ctoken/compress_or_decompress_ctokens.rs b/programs/compressed-token/program/src/transfer2/compression/ctoken/compress_or_decompress_ctokens.rs index c8ec72b2c2..54de5bf7b3 100644 --- a/programs/compressed-token/program/src/transfer2/compression/ctoken/compress_or_decompress_ctokens.rs +++ b/programs/compressed-token/program/src/transfer2/compression/ctoken/compress_or_decompress_ctokens.rs @@ -136,6 +136,7 @@ fn process_compressible_extension( .slot; } *transfer_amount = compressible_extension + .info .calculate_top_up_lamports( token_account_info.data_len() as u64, *current_slot, diff --git a/programs/compressed-token/program/tests/compress_and_close.rs b/programs/compressed-token/program/tests/compress_and_close.rs index aaed6f021f..e3b5b885ec 100644 --- a/programs/compressed-token/program/tests/compress_and_close.rs +++ b/programs/compressed-token/program/tests/compress_and_close.rs @@ -39,11 +39,17 @@ fn create_compressible_ctoken_data( if let Some(light_ctoken_interface::state::ZExtensionStructMut::Compressible(comp_ext)) = extensions.first_mut() { - comp_ext.config_account_version.set(1); - comp_ext.account_version = 3; // ShaFlat - comp_ext.compression_authority.copy_from_slice(owner_pubkey); - comp_ext.rent_sponsor.copy_from_slice(rent_sponsor_pubkey); - comp_ext.last_claimed_slot.set(0); + comp_ext.info.config_account_version.set(1); + comp_ext.info.account_version = 3; // ShaFlat + comp_ext + .info + .compression_authority + .copy_from_slice(owner_pubkey); + comp_ext + .info + .rent_sponsor + .copy_from_slice(rent_sponsor_pubkey); + comp_ext.info.last_claimed_slot.set(0); } } diff --git a/sdk-libs/ctoken-sdk/src/compressed_token/v2/compress_and_close.rs b/sdk-libs/ctoken-sdk/src/compressed_token/v2/compress_and_close.rs index 1a806eb363..5c465ddd73 100644 --- a/sdk-libs/ctoken-sdk/src/compressed_token/v2/compress_and_close.rs +++ b/sdk-libs/ctoken-sdk/src/compressed_token/v2/compress_and_close.rs @@ -59,12 +59,12 @@ pub fn pack_for_compress_and_close( for extension in extensions { if let ZExtensionStruct::Compressible(e) = extension { authority_index = packed_accounts.insert_or_get_config( - Pubkey::from(e.compression_authority), + Pubkey::from(e.info.compression_authority), true, true, ); recipient_index = - packed_accounts.insert_or_get(Pubkey::from(e.rent_sponsor)); + packed_accounts.insert_or_get(Pubkey::from(e.info.rent_sponsor)); break; } @@ -80,7 +80,7 @@ pub fn pack_for_compress_and_close( for extension in extensions { if let ZExtensionStruct::Compressible(e) = extension { recipient_index = - packed_accounts.insert_or_get(Pubkey::from(e.rent_sponsor)); + packed_accounts.insert_or_get(Pubkey::from(e.info.rent_sponsor)); break; } @@ -326,8 +326,9 @@ pub fn compress_and_close_ctoken_accounts<'info>( for extension in extensions { if let ZExtensionStruct::Compressible(extension) = extension { // Check if compression_authority is set (non-zero) - if extension.compression_authority != [0u8; 32] { - compression_authority = Pubkey::from(extension.compression_authority); + if extension.info.compression_authority != [0u8; 32] { + compression_authority = + Pubkey::from(extension.info.compression_authority); } break; } @@ -348,8 +349,8 @@ pub fn compress_and_close_ctoken_accounts<'info>( for extension in extensions { if let ZExtensionStruct::Compressible(ext) = extension { // Check if rent_sponsor is set (non-zero) - if ext.rent_sponsor != [0u8; 32] { - rent_sponsor_pubkey = Some(Pubkey::from(ext.rent_sponsor)); + if ext.info.rent_sponsor != [0u8; 32] { + rent_sponsor_pubkey = Some(Pubkey::from(ext.info.rent_sponsor)); } break; } diff --git a/sdk-libs/program-test/src/compressible.rs b/sdk-libs/program-test/src/compressible.rs index 6753a79545..f672404bac 100644 --- a/sdk-libs/program-test/src/compressible.rs +++ b/sdk-libs/program-test/src/compressible.rs @@ -107,6 +107,7 @@ pub async fn claim_and_compress( .await .unwrap(); let last_funded_epoch = e + .info .get_last_funded_epoch( account.1.data.len() as u64, account.1.lamports, @@ -150,11 +151,12 @@ pub async fn claim_and_compress( num_bytes: account.data.len() as u64, current_slot, current_lamports: account.lamports, - last_claimed_slot: comp_ext.last_claimed_slot, + last_claimed_slot: comp_ext.info.last_claimed_slot, }; // Check what action is needed - match state.calculate_claimable_rent(&comp_ext.rent_config, rent_exemption) { + match state.calculate_claimable_rent(&comp_ext.info.rent_config, rent_exemption) + { None => { // Account is compressible (has rent deficit) compress_accounts.push(*pubkey); diff --git a/sdk-libs/program-test/src/forester/compress_and_close_forester.rs b/sdk-libs/program-test/src/forester/compress_and_close_forester.rs index d0abd837e2..ddbc504034 100644 --- a/sdk-libs/program-test/src/forester/compress_and_close_forester.rs +++ b/sdk-libs/program-test/src/forester/compress_and_close_forester.rs @@ -127,14 +127,14 @@ pub async fn compress_and_close_forester( if let Some(extensions) = &ctoken_account.extensions { for extension in extensions { if let ZExtensionStruct::Compressible(e) = extension { - let current_authority = Pubkey::from(e.compression_authority); - rent_sponsor_pubkey = Pubkey::from(e.rent_sponsor); + let current_authority = Pubkey::from(e.info.compression_authority); + rent_sponsor_pubkey = Pubkey::from(e.info.rent_sponsor); if compression_authority_pubkey.is_none() { compression_authority_pubkey = Some(current_authority); } - if e.compress_to_pubkey() { + if e.info.compress_to_pubkey() { compressed_token_owner = *solana_ctoken_account_pubkey; } break; diff --git a/sdk-libs/token-client/src/instructions/transfer2.rs b/sdk-libs/token-client/src/instructions/transfer2.rs index bf4ed3a60a..64622564e7 100644 --- a/sdk-libs/token-client/src/instructions/transfer2.rs +++ b/sdk-libs/token-client/src/instructions/transfer2.rs @@ -523,10 +523,11 @@ pub async fn create_generic_transfer2_instruction( let mut found_compress_to_pubkey = false; for extension in extensions { if let ZExtensionStruct::Compressible(compressible_ext) = extension { - found_rent_sponsor = Some(compressible_ext.rent_sponsor); + found_rent_sponsor = Some(compressible_ext.info.rent_sponsor); found_compression_authority = - Some(compressible_ext.compression_authority); - found_compress_to_pubkey = compressible_ext.compress_to_pubkey == 1; + Some(compressible_ext.info.compression_authority); + found_compress_to_pubkey = + compressible_ext.info.compress_to_pubkey == 1; break; } }