Skip to content

Commit 885f5e7

Browse files
committed
stash approve impl, refactored account_infos to be more generic
1 parent dac75f7 commit 885f5e7

File tree

14 files changed

+677
-88
lines changed

14 files changed

+677
-88
lines changed

program-tests/sdk-token-test/src/lib.rs

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,25 @@
11
#![allow(unexpected_cfgs)]
22

33
use anchor_lang::{prelude::*, solana_program::program::invoke, Discriminator};
4+
use light_compressed_token_sdk::instructions::transfer::instruction::DecompressInputs;
5+
use light_compressed_token_sdk::instructions::Recipient;
46
use light_compressed_token_sdk::{
57
account::CTokenAccount,
6-
cpi_account_infos::TransferAccountInfos,
7-
instructions::transfer::instruction::{
8-
compress, decompress, transfer, CompressInputs, TransferInputs,
8+
instructions::{
9+
batch_compress::{create_batch_compress_instruction, BatchCompressInputs},
10+
transfer::{
11+
instruction::{compress, decompress, transfer, CompressInputs, TransferInputs},
12+
TransferAccountInfos,
13+
},
914
},
1015
TokenAccountMeta, ValidityProof,
1116
};
17+
1218
declare_id!("5p1t1GAaKtK1FKCh5Hd2Gu8JCu3eREhJm4Q2qYfTEPYK");
1319

1420
#[program]
1521
pub mod sdk_token_test {
1622

17-
use light_compressed_token_sdk::instructions::transfer::instruction::DecompressInputs;
18-
1923
use super::*;
2024

2125
pub fn compress_tokens<'info>(
@@ -137,6 +141,57 @@ pub mod sdk_token_test {
137141

138142
Ok(())
139143
}
144+
145+
pub fn batch_compress_tokens<'info>(
146+
ctx: Context<'_, '_, '_, 'info, Generic<'info>>,
147+
recipients: Vec<Recipient>,
148+
_output_tree_index: u8,
149+
_mint: Pubkey,
150+
token_pool_index: u8,
151+
token_pool_bump: u8,
152+
) -> Result<()> {
153+
let light_cpi_accounts = TransferAccountInfos::new_compress(
154+
ctx.accounts.signer.as_ref(),
155+
ctx.accounts.signer.as_ref(),
156+
ctx.remaining_accounts,
157+
);
158+
159+
// Convert local Recipient to SDK Recipient
160+
let sdk_recipients: Vec<
161+
light_compressed_token_sdk::instructions::batch_compress::Recipient,
162+
> = recipients
163+
.into_iter()
164+
.map(
165+
|r| light_compressed_token_sdk::instructions::batch_compress::Recipient {
166+
pubkey: r.pubkey,
167+
amount: r.amount,
168+
},
169+
)
170+
.collect();
171+
172+
let batch_compress_inputs = BatchCompressInputs {
173+
fee_payer: *ctx.accounts.signer.key,
174+
authority: *ctx.accounts.signer.key,
175+
token_pool_pda: *light_cpi_accounts.token_pool_pda().unwrap().key,
176+
sender_token_account: *light_cpi_accounts.sender_token_account().unwrap().key,
177+
token_program: *light_cpi_accounts.spl_token_program().unwrap().key,
178+
merkle_tree: *light_cpi_accounts.tree_accounts().unwrap()[0].key,
179+
recipients: sdk_recipients,
180+
lamports: None,
181+
token_pool_index,
182+
token_pool_bump,
183+
sol_pool_pda: None,
184+
};
185+
186+
let instruction =
187+
create_batch_compress_instruction(batch_compress_inputs).map_err(ProgramError::from)?;
188+
msg!("batch compress instruction {:?}", instruction);
189+
let account_infos = light_cpi_accounts.to_account_infos();
190+
191+
invoke(&instruction, account_infos.as_slice())?;
192+
193+
Ok(())
194+
}
140195
}
141196

142197
#[derive(Accounts)]

program-tests/sdk-token-test/tests/test.rs

Lines changed: 174 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@
33
use anchor_lang::{AccountDeserialize, InstructionData};
44
use anchor_spl::token::TokenAccount;
55
use light_compressed_token_sdk::{
6-
instructions::transfer::account_metas::{
7-
get_transfer_instruction_account_metas, TokenAccountsMetaConfig,
6+
instructions::{
7+
batch_compress::{
8+
get_batch_compress_instruction_account_metas, BatchCompressMetaConfig, Recipient,
9+
},
10+
transfer::account_metas::{
11+
get_transfer_instruction_account_metas, TokenAccountsMetaConfig,
12+
},
813
},
914
token_pool::get_token_pool_pda,
1015
TokenAccountMeta, SPL_TOKEN_PROGRAM_ID,
@@ -447,3 +452,170 @@ async fn decompress_compressed_tokens(
447452
rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[payer])
448453
.await
449454
}
455+
456+
#[tokio::test]
457+
async fn test_batch_compress() {
458+
// Initialize the test environment
459+
let mut rpc = LightProgramTest::new(ProgramTestConfig::new_v2(
460+
false,
461+
Some(vec![("sdk_token_test", sdk_token_test::ID)]),
462+
))
463+
.await
464+
.unwrap();
465+
466+
let payer = rpc.get_payer().insecure_clone();
467+
468+
// Create a mint
469+
let mint_pubkey = create_mint_helper(&mut rpc, &payer).await;
470+
println!("Created mint: {}", mint_pubkey);
471+
472+
// Create a token account
473+
let token_account_keypair = Keypair::new();
474+
475+
create_token_account(&mut rpc, &mint_pubkey, &token_account_keypair, &payer)
476+
.await
477+
.unwrap();
478+
479+
println!("Created token account: {}", token_account_keypair.pubkey());
480+
481+
// Mint some tokens to the account
482+
let mint_amount = 2_000_000; // 2000 tokens with 6 decimals
483+
484+
mint_spl_tokens(
485+
&mut rpc,
486+
&mint_pubkey,
487+
&token_account_keypair.pubkey(),
488+
&payer.pubkey(), // owner
489+
&payer, // mint authority
490+
mint_amount,
491+
false, // not token22
492+
)
493+
.await
494+
.unwrap();
495+
496+
println!("Minted {} tokens to account", mint_amount);
497+
498+
// Create multiple recipients for batch compression
499+
let recipient1 = Keypair::new().pubkey();
500+
let recipient2 = Keypair::new().pubkey();
501+
let recipient3 = Keypair::new().pubkey();
502+
503+
let recipients = vec![
504+
Recipient {
505+
pubkey: recipient1,
506+
amount: 100_000,
507+
},
508+
Recipient {
509+
pubkey: recipient2,
510+
amount: 200_000,
511+
},
512+
Recipient {
513+
pubkey: recipient3,
514+
amount: 300_000,
515+
},
516+
];
517+
518+
let total_batch_amount: u64 = recipients.iter().map(|r| r.amount).sum();
519+
520+
// Perform batch compression
521+
batch_compress_spl_tokens(
522+
&mut rpc,
523+
&payer,
524+
recipients,
525+
mint_pubkey,
526+
token_account_keypair.pubkey(),
527+
)
528+
.await
529+
.unwrap();
530+
531+
println!(
532+
"Batch compressed {} tokens to {} recipients successfully",
533+
total_batch_amount, 3
534+
);
535+
536+
// Verify each recipient received their compressed tokens
537+
for (i, recipient) in [recipient1, recipient2, recipient3].iter().enumerate() {
538+
let compressed_accounts = rpc
539+
.indexer()
540+
.unwrap()
541+
.get_compressed_token_accounts_by_owner(recipient, None, None)
542+
.await
543+
.unwrap()
544+
.value
545+
.items;
546+
547+
assert!(
548+
!compressed_accounts.is_empty(),
549+
"Recipient {} should have compressed tokens",
550+
i + 1
551+
);
552+
553+
let compressed_account = &compressed_accounts[0];
554+
assert_eq!(compressed_account.token.owner, *recipient);
555+
assert_eq!(compressed_account.token.mint, mint_pubkey);
556+
557+
let expected_amount = match i {
558+
0 => 100_000,
559+
1 => 200_000,
560+
2 => 300_000,
561+
_ => unreachable!(),
562+
};
563+
assert_eq!(compressed_account.token.amount, expected_amount);
564+
565+
println!(
566+
"Verified recipient {} received {} compressed tokens",
567+
i + 1,
568+
compressed_account.token.amount
569+
);
570+
}
571+
572+
println!("Batch compression test completed successfully!");
573+
}
574+
575+
async fn batch_compress_spl_tokens(
576+
rpc: &mut LightProgramTest,
577+
payer: &Keypair,
578+
recipients: Vec<Recipient>,
579+
mint: Pubkey,
580+
token_account: Pubkey,
581+
) -> Result<Signature, RpcError> {
582+
let mut remaining_accounts = PackedAccounts::default();
583+
let token_pool_pda = get_token_pool_pda(&mint);
584+
println!("token_pool_pda {:?}", token_pool_pda);
585+
// Use batch compress account metas
586+
let config = BatchCompressMetaConfig::new_client(
587+
token_pool_pda,
588+
token_account,
589+
SPL_TOKEN_PROGRAM_ID.into(),
590+
rpc.get_random_state_tree_info().unwrap().tree,
591+
false, // with_lamports
592+
);
593+
594+
remaining_accounts.add_pre_accounts_signer_mut(payer.pubkey());
595+
let metas = get_batch_compress_instruction_account_metas(config);
596+
remaining_accounts.add_pre_accounts_metas(metas.as_slice());
597+
598+
let output_tree_index = rpc
599+
.get_random_state_tree_info()
600+
.unwrap()
601+
.pack_output_tree_index(&mut remaining_accounts)
602+
.unwrap();
603+
604+
let (remaining_accounts, _, _) = remaining_accounts.to_account_metas();
605+
606+
let instruction = Instruction {
607+
program_id: sdk_token_test::ID,
608+
accounts: [remaining_accounts].concat(),
609+
data: sdk_token_test::instruction::BatchCompressTokens {
610+
recipients,
611+
_output_tree_index: output_tree_index,
612+
_mint: mint,
613+
token_pool_index: 0, // Default pool index
614+
token_pool_bump: 255, // Default bump
615+
}
616+
.data(),
617+
};
618+
619+
rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[payer])
620+
.await
621+
}

sdk-libs/compressed-token-sdk/src/account.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,45 @@ impl CTokenAccount {
9393
})
9494
}
9595

96+
/// Approves a delegate for a specified amount of tokens.
97+
/// Similar to transfer, this deducts the amount from the current account
98+
/// and returns a new CTokenAccount that represents the delegated portion.
99+
/// The original account balance is reduced by the delegated amount.
100+
pub fn approve(
101+
&mut self,
102+
_delegate: &Pubkey,
103+
amount: u64,
104+
output_merkle_tree_index: Option<u8>,
105+
) -> Result<Self, TokenSdkError> {
106+
if amount > self.output.amount {
107+
return Err(TokenSdkError::InsufficientBalance);
108+
}
109+
110+
// Deduct the delegated amount from current account
111+
self.output.amount -= amount;
112+
let merkle_tree_index = output_merkle_tree_index.unwrap_or(self.output.merkle_tree_index);
113+
114+
self.method_used = true;
115+
116+
// Create a new delegated account with the specified delegate
117+
// Note: In the actual instruction, this will create the proper delegation structure
118+
Ok(Self {
119+
compression_amount: None,
120+
is_compress: false,
121+
is_decompress: false,
122+
inputs: vec![],
123+
output: PackedTokenTransferOutputData {
124+
owner: self.output.owner, // Owner remains the same, but delegate is set
125+
amount,
126+
lamports: None,
127+
tlv: None,
128+
merkle_tree_index,
129+
},
130+
mint: self.mint,
131+
method_used: true,
132+
})
133+
}
134+
96135
// TODO: consider this might be confusing because it must not be used in combination with fn compress()
97136
pub fn compress(&mut self, amount: u64) -> Result<(), TokenSdkError> {
98137
self.output.amount += amount;

sdk-libs/compressed-token-sdk/src/instructions/approve.rs

Whitespace-only changes.

0 commit comments

Comments
 (0)