Skip to content

Commit 765ad11

Browse files
committed
feat: compressed token sdk
mint to spl works compress works token transfer works decompress works batch compress works refactor: sdk-token-test ixs into files with cpi context works fix: get_validity_proof order fix: process_update_deposit typo format stash add process_four_invokes add create escrow pda ix stash test created escrow pda stash four invocations test fails on 4th cpi four cpi test works refactor: light-sdks detach account metas from account infos
1 parent b5e7363 commit 765ad11

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+6463
-74
lines changed

Cargo.lock

Lines changed: 58 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ members = [
2525
"sdk-libs/sdk-types",
2626
"sdk-libs/photon-api",
2727
"sdk-libs/program-test",
28+
"sdk-libs/compressed-token-types",
29+
"sdk-libs/compressed-token-sdk",
2830
"xtask",
2931
"examples/anchor/token-escrow",
3032
# "examples/anchor/name-service-without-macros",
@@ -40,6 +42,7 @@ members = [
4042
# Issue is that anchor discriminator now returns a slice instead of an array
4143
"program-tests/sdk-anchor-test/programs/sdk-anchor-test",
4244
"program-tests/sdk-test",
45+
"program-tests/sdk-token-test",
4346
"program-tests/sdk-pinocchio-test",
4447
"program-tests/create-address-test-program",
4548
"program-tests/utils",
@@ -60,6 +63,10 @@ strip = "none"
6063
[profile.release]
6164
overflow-checks = true
6265

66+
[workspace.package]
67+
version = "0.1.0"
68+
edition = "2021"
69+
6370
[workspace.dependencies]
6471
solana-banks-client = { version = "2.2" }
6572
solana-banks-interface = { version = "2.2" }
@@ -175,6 +182,8 @@ account-compression = { path = "programs/account-compression", version = "2.0.0"
175182
light-compressed-token = { path = "programs/compressed-token", version = "2.0.0", features = [
176183
"cpi",
177184
] }
185+
light-compressed-token-types = { path = "sdk-libs/compressed-token-types", name = "light-compressed-token-types" }
186+
light-compressed-token-sdk = { path = "sdk-libs/compressed-token-sdk" }
178187
light-system-program-anchor = { path = "anchor-programs/system", version = "2.0.0", features = [
179188
"cpi",
180189
] }

program-libs/compressed-account/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ light-zero-copy = { workspace = true, features = ["std"] }
2222
light-macros = { workspace = true }
2323
pinocchio = { workspace = true, optional = true }
2424
solana-program-error = { workspace = true, optional = true }
25-
25+
solana-msg = { workspace = true }
2626
# Feature-gated dependencies
2727
anchor-lang = { workspace = true, optional = true }
2828
bytemuck = { workspace = true, optional = true, features = ["derive"] }

program-libs/compressed-account/src/compressed_account.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,6 @@ pub fn hash_with_hashed_values(
295295
vec.push(&discriminator_bytes);
296296
vec.push(data_hash);
297297
}
298-
299298
Ok(Poseidon::hashv(&vec)?)
300299
}
301300

program-tests/create-address-test-program/src/lib.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,7 @@ pub mod system_cpi_test {
8080
let cpi_accounts =
8181
CpiAccounts::new_with_config(&fee_payer, ctx.remaining_accounts, config);
8282

83-
let account_infos = cpi_accounts
84-
.to_account_infos()
85-
.into_iter()
86-
.cloned()
87-
.collect::<Vec<_>>();
83+
let account_infos = cpi_accounts.to_account_infos();
8884

8985
let config = CpiInstructionConfig::try_from(&cpi_accounts)
9086
.map_err(|_| ErrorCode::AccountNotEnoughKeys)?;

program-tests/sdk-test/src/create_pda.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ pub fn create_pda<const BATCHED: bool>(
2525
&accounts[0],
2626
&accounts[instruction_data.system_accounts_offset as usize..],
2727
config,
28-
)
29-
.unwrap();
28+
)?;
3029

3130
let address_tree_info = instruction_data.address_tree_info;
3231
let (address, address_seed) = if BATCHED {
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
# SDK Token Test Debugging Guide
2+
3+
## Error Code Reference
4+
5+
| Error Code | Error Name | Description | Common Fix |
6+
|------------|------------|-------------|------------|
7+
| 16031 | `CpiAccountsIndexOutOfBounds` | Missing account in accounts array | Add signer with `add_pre_accounts_signer_mut()` |
8+
| 6020 | `CpiContextAccountUndefined` | CPI context expected but not provided | Set `cpi_context: None` for simple operations |
9+
10+
### Light System Program Errors (Full Reference)
11+
| 6017 | `ProofIsNone` | 6018 | `ProofIsSome` | 6019 | `EmptyInputs` | 6020 | `CpiContextAccountUndefined` |
12+
| 6021 | `CpiContextEmpty` | 6022 | `CpiContextMissing` | 6023 | `DecompressionRecipientDefined` |
13+
14+
## Common Issues and Solutions
15+
16+
### 1. `CpiAccountsIndexOutOfBounds` (Error 16031)
17+
Missing signer account. **Fix**: `remaining_accounts.add_pre_accounts_signer_mut(payer.pubkey())`
18+
19+
### 2. Privilege Escalation Error
20+
Manually adding accounts instead of using PackedAccounts. **Fix**: Use `add_pre_accounts_signer_mut()` instead of manual account concatenation.
21+
22+
### 3. Account Structure Mismatch
23+
Wrong context type. **Fix**: Use `Generic<'info>` for single signer, `GenericWithAuthority<'info>` for signer + authority.
24+
25+
### 4. `CpiContextAccountUndefined` (Error 6020)
26+
**Root Cause**: Using functions designed for CPI context when you don't need it.
27+
28+
**CPI Context Purpose**: Optimize multi-program transactions by using one proof instead of multiple. Flow:
29+
1. First program: Cache signer checks in CPI context
30+
2. Second program: Read context, combine data, execute with single proof
31+
32+
**Solutions**:
33+
```rust
34+
// ✅ Simple operations - no CPI context
35+
let cpi_inputs = CpiInputs {
36+
proof,
37+
account_infos: Some(vec![account.to_account_info().unwrap()]),
38+
new_addresses: Some(vec![new_address_params]),
39+
cpi_context: None, // ← Key
40+
..Default::default()
41+
};
42+
43+
// ✅ Complex multi-program operations - use CPI context
44+
let config = SystemAccountMetaConfig::new_with_cpi_context(program_id, cpi_context_account);
45+
```
46+
47+
### 5. Avoid Complex Function Reuse
48+
**Problem**: Functions like `process_create_compressed_account` expect CPI context setup.
49+
50+
**Fix**: Use direct Light SDK approach:
51+
```rust
52+
// ❌ Complex function with CPI context dependency
53+
process_create_compressed_account(...)
54+
55+
// ✅ Direct approach
56+
let mut account = LightAccount::<'_, CompressedEscrowPda>::new_init(&crate::ID, Some(address), tree_index);
57+
account.amount = amount;
58+
account.owner = *cpi_accounts.fee_payer().key;
59+
let cpi_inputs = CpiInputs { proof, account_infos: Some(vec![account.to_account_info().unwrap()]), cpi_context: None, ..Default::default() };
60+
cpi_inputs.invoke_light_system_program(cpi_accounts)
61+
```
62+
63+
### 6. Critical Four Invokes Implementation Learnings
64+
65+
**CompressInputs Structure for CPI Context Operations**:
66+
```rust
67+
let compress_inputs = CompressInputs {
68+
fee_payer: *cpi_accounts.fee_payer().key,
69+
authority: *cpi_accounts.fee_payer().key,
70+
mint,
71+
recipient,
72+
sender_token_account: *remaining_accounts[0].key, // ← Use remaining_accounts index
73+
amount,
74+
output_tree_index,
75+
// ❌ Wrong: output_queue_pubkey: *cpi_accounts.tree_accounts().unwrap()[0].key,
76+
token_pool_pda: *remaining_accounts[1].key, // ← From remaining_accounts
77+
transfer_config: Some(TransferConfig {
78+
cpi_context: Some(CompressedCpiContext {
79+
set_context: true,
80+
first_set_context: true,
81+
cpi_context_account_index: 0,
82+
}),
83+
cpi_context_pubkey: Some(cpi_context_pubkey),
84+
..Default::default()
85+
}),
86+
spl_token_program: *remaining_accounts[2].key, // ← SPL_TOKEN_PROGRAM_ID
87+
tree_accounts: cpi_accounts.tree_pubkeys().unwrap(), // ← From CPI accounts
88+
};
89+
```
90+
91+
**Critical Account Ordering for Four Invokes**:
92+
```rust
93+
// Test setup - exact order matters for remaining_accounts indices
94+
remaining_accounts.add_pre_accounts_signer_mut(payer.pubkey());
95+
// Remaining accounts 0 - compression token account
96+
remaining_accounts.add_pre_accounts_meta(AccountMeta::new(compression_token_account, false));
97+
// Remaining accounts 1 - token pool PDA
98+
remaining_accounts.add_pre_accounts_meta(AccountMeta::new(token_pool_pda1, false));
99+
// Remaining accounts 2 - SPL token program
100+
remaining_accounts.add_pre_accounts_meta(AccountMeta::new(SPL_TOKEN_PROGRAM_ID.into(), false));
101+
// Remaining accounts 3 - compressed token program
102+
remaining_accounts.add_pre_accounts_meta(AccountMeta::new(compressed_token_program, false));
103+
// Remaining accounts 4 - CPI authority PDA
104+
remaining_accounts.add_pre_accounts_meta(AccountMeta::new(cpi_authority_pda, false));
105+
```
106+
107+
**Validity Proof and Tree Info Management**:
108+
```rust
109+
// Get escrow account directly by address (more efficient)
110+
let escrow_account = rpc.get_compressed_account(escrow_address, None).await?.value;
111+
112+
// Pack tree infos BEFORE constructing TokenAccountMeta
113+
let packed_tree_info = rpc_result.pack_tree_infos(&mut remaining_accounts);
114+
115+
// Use correct tree info indices for each compressed account
116+
let mint2_tree_info = packed_tree_info.state_trees.as_ref().unwrap().packed_tree_infos[1];
117+
let mint3_tree_info = packed_tree_info.state_trees.as_ref().unwrap().packed_tree_infos[2];
118+
let escrow_tree_info = packed_tree_info.state_trees.as_ref().unwrap().packed_tree_infos[0];
119+
```
120+
121+
**System Accounts Start Offset**:
122+
```rust
123+
// Use the actual offset returned by to_account_metas()
124+
let (accounts, system_accounts_start_offset, _) = remaining_accounts.to_account_metas();
125+
// Pass this offset to the instruction
126+
system_accounts_start_offset: system_accounts_start_offset as u8,
127+
```
128+
129+
## Best Practices
130+
131+
### CPI Context Decision
132+
- **Use**: Multi-program transactions with compressed accounts (saves proofs)
133+
- **Avoid**: Simple single-program operations (PDA creation, basic transfers)
134+
135+
### Account Management
136+
- Use `PackedAccounts` and `add_pre_accounts_signer_mut()`
137+
- Choose `Generic<'info>` (1 account) vs `GenericWithAuthority<'info>` (2 accounts)
138+
- Set `cpi_context: None` for simple operations
139+
140+
### Working Patterns
141+
```rust
142+
// Compress tokens pattern
143+
let mut remaining_accounts = PackedAccounts::default();
144+
remaining_accounts.add_pre_accounts_signer_mut(payer.pubkey());
145+
let metas = get_transfer_instruction_account_metas(config);
146+
remaining_accounts.add_pre_accounts_metas(metas.as_slice());
147+
let output_tree_index = rpc.get_random_state_tree_info().unwrap().pack_output_tree_index(&mut remaining_accounts).unwrap();
148+
149+
// Test flow: Setup → Compress → Create PDA → Execute
150+
```
151+
152+
## Implementation Status
153+
154+
### ✅ Working Features
155+
1. **Basic PDA Creation**: `create_escrow_pda` instruction works correctly
156+
2. **Token Compression**: Individual token compression operations work
157+
3. **Four Invokes Instruction**: Complete CPI context implementation working
158+
- Account structure: Uses `Generic<'info>` (single signer)
159+
- CPI context: Proper multi-program proof optimization
160+
- Token accounts: Correct account ordering and tree info management
161+
- Compress CPI: Working with proper `CompressInputs` structure
162+
- Transfer CPI: Custom `transfer_tokens_with_cpi_context` wrapper replaces `transfer_tokens_to_escrow_pda`
163+
4. **Error Handling**: Comprehensive error code documentation and fixes
164+
165+
### Key Implementation Success
166+
The `four_invokes` instruction successfully demonstrates the complete CPI context pattern for Light Protocol, enabling:
167+
- **Single Proof Optimization**: One validity proof for multiple compressed account operations
168+
- **Cross-Program Integration**: Token program + system program coordination
169+
- **Production Ready**: Complete account setup and tree info management
170+
- **Custom Transfer Wrapper**: Purpose-built transfer function for four invokes instruction
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
[package]
2+
name = "sdk-token-test"
3+
version = "1.0.0"
4+
description = "Test program using compressed token SDK"
5+
repository = "https://github.com/Lightprotocol/light-protocol"
6+
license = "Apache-2.0"
7+
edition = "2021"
8+
9+
[lib]
10+
crate-type = ["cdylib", "lib"]
11+
name = "sdk_token_test"
12+
13+
[features]
14+
no-entrypoint = []
15+
no-idl = []
16+
no-log-ix-name = []
17+
cpi = ["no-entrypoint"]
18+
test-sbf = []
19+
default = []
20+
21+
[dependencies]
22+
light-compressed-token-sdk = { workspace = true, features = ["anchor"] }
23+
anchor-lang = { workspace = true }
24+
light-hasher = { workspace = true }
25+
light-sdk = { workspace = true }
26+
light-sdk-types = { workspace = true }
27+
light-compressed-account = { workspace = true }
28+
arrayvec = { workspace = true }
29+
light-batched-merkle-tree = { workspace = true }
30+
31+
[dev-dependencies]
32+
light-program-test = { workspace = true, features = ["devenv"] }
33+
light-test-utils = { workspace = true }
34+
tokio = { workspace = true }
35+
serial_test = { workspace = true }
36+
solana-sdk = { workspace = true }
37+
anchor-spl = { workspace = true }
38+
light-sdk = { workspace = true }
39+
light-compressed-account = { workspace = true, features = ["anchor"] }
40+
light-client = { workspace = true }
41+
42+
[lints.rust.unexpected_cfgs]
43+
level = "allow"
44+
check-cfg = [
45+
'cfg(target_os, values("solana"))',
46+
'cfg(feature, values("frozen-abi", "no-entrypoint"))',
47+
]
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[target.bpfel-unknown-unknown.dependencies.std]
2+
features = []

0 commit comments

Comments
 (0)