Skip to content

Commit 080a75d

Browse files
wip
add compress_pda helper compress_pda compiling decompress_idempotent.rs wip wip decompress batch idempotent wip add compress_pda_new and compress_multiple_pdas_new native program with decompress done compress_dynamic, decompress_dynamic wip adding anchor testprogram uses sdk fix compilation wip experiment with procmacro skip SLOT check at compress_pda_new wip add_compressible_instructions() works for compress, idl gen works add proc macro decompress_multiple_pdas is working as it should fix decompress_idempotent impl rm expanded add remove_data force apps to pass the whole signer_seeds directly add compressible_config draft add create_config_unchecked and checked use config, add unified header struct (just last_written_slot) for now use hascompressioninfo and compressioninfo add config support to compressible macro add expanded.rs cleanup anchor-derived example add support for multiple address_trees per address space add support for multiple address_trees per address space update macro to multiple address_trees add test-sdk-derived program wip cleanup native macro-derive example wip fix compilation config tests working clean up test_config.rs testing add a separate anchor compress_pda_new version so we dont have redundant serde wip wip wip fix decompress_idempotent anchor add test with 2nd account decompress works with multiple different PDAs cleanup, remove anchor helper decompress_multiple_pdas, fix discriminator writes
1 parent aad94a4 commit 080a75d

Some content is hidden

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

57 files changed

+16275
-540
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ members = [
4848
"forester-utils",
4949
"forester",
5050
"sparse-merkle-tree",
51+
"program-tests/anchor-compressible-user",
52+
"program-tests/anchor-compressible-user-derived",
53+
"program-tests/sdk-test-derived",
5154
]
5255

5356
resolver = "2"
@@ -90,6 +93,7 @@ solana-transaction = { version = "2.2" }
9093
solana-transaction-error = { version = "2.2" }
9194
solana-hash = { version = "2.2" }
9295
solana-clock = { version = "2.2" }
96+
solana-rent = { version = "2.2" }
9397
solana-signature = { version = "2.2" }
9498
solana-commitment-config = { version = "2.2" }
9599
solana-account = { version = "2.2" }

pnpm-lock.yaml

Lines changed: 3 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
[package]
2+
name = "anchor-compressible-user-derived"
3+
version = "0.1.0"
4+
description = "Anchor program template with user records and derived accounts"
5+
edition = "2021"
6+
7+
[lib]
8+
crate-type = ["cdylib", "lib"]
9+
name = "anchor_compressible_user_derived"
10+
11+
[features]
12+
no-entrypoint = []
13+
no-idl = []
14+
no-log-ix-name = []
15+
cpi = ["no-entrypoint"]
16+
default = ["idl-build"]
17+
idl-build = ["anchor-lang/idl-build", "light-sdk/idl-build"]
18+
19+
test-sbf = []
20+
21+
22+
[dependencies]
23+
light-sdk = { workspace = true, features = ["anchor", "idl-build"] }
24+
light-sdk-types = { workspace = true }
25+
light-sdk-macros = { workspace = true }
26+
light-hasher = { workspace = true, features = ["solana"] }
27+
solana-program = { workspace = true }
28+
light-macros = { workspace = true, features = ["solana"] }
29+
borsh = { workspace = true }
30+
light-compressed-account = { workspace = true, features = ["solana"] }
31+
anchor-lang = { workspace = true, features = ["idl-build"] }
32+
33+
[dev-dependencies]
34+
light-program-test = { workspace = true, features = ["devenv"] }
35+
light-client = { workspace = true, features = ["devenv"] }
36+
light-test-utils = { workspace = true, features = ["devenv"] }
37+
tokio = { workspace = true }
38+
solana-sdk = { workspace = true }
39+
40+
[lints.rust.unexpected_cfgs]
41+
level = "allow"
42+
check-cfg = [
43+
'cfg(target_os, values("solana"))',
44+
'cfg(feature, values("frozen-abi", "no-entrypoint"))',
45+
]
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
# Example: Using the add_compressible_instructions Macro
2+
3+
This example shows how to use the `add_compressible_instructions` macro to automatically generate compression-related instructions for your Anchor program.
4+
5+
## Basic Setup
6+
7+
```rust
8+
use anchor_lang::prelude::*;
9+
use light_sdk::{
10+
compressible::{CompressionInfo, HasCompressionInfo},
11+
derive_light_cpi_signer, LightDiscriminator, LightHasher,
12+
};
13+
use light_sdk_macros::add_compressible_instructions;
14+
15+
declare_id!("YourProgramId11111111111111111111111111111");
16+
17+
// Define your CPI signer
18+
pub const LIGHT_CPI_SIGNER: CpiSigner =
19+
derive_light_cpi_signer!("YourCpiSignerPubkey11111111111111111111111");
20+
21+
// Apply the macro to your program module
22+
#[add_compressible_instructions(UserRecord, GameSession)]
23+
#[program]
24+
pub mod my_program {
25+
use super::*;
26+
27+
// The macro automatically generates these instructions:
28+
// - create_compression_config (config management)
29+
// - update_compression_config (config management)
30+
// - compress_user_record (compress existing PDA)
31+
// - compress_game_session (compress existing PDA)
32+
// - decompress_multiple_pdas (decompress compressed accounts)
33+
//
34+
// NOTE: create_user_record and create_game_session are NOT generated
35+
// because they typically need custom initialization logic
36+
37+
// You can still add your own custom instructions here
38+
}
39+
```
40+
41+
## Define Your Account Structures
42+
43+
```rust
44+
#[derive(Debug, LightHasher, LightDiscriminator, Default)]
45+
#[account]
46+
pub struct UserRecord {
47+
#[skip] // Skip compression_info from hashing
48+
pub compression_info: CompressionInfo,
49+
#[hash] // Include in hash
50+
pub owner: Pubkey,
51+
#[hash]
52+
pub name: String,
53+
pub score: u64,
54+
}
55+
56+
// Implement the required trait
57+
impl HasCompressionInfo for UserRecord {
58+
fn compression_info(&self) -> &CompressionInfo {
59+
&self.compression_info
60+
}
61+
62+
fn compression_info_mut(&mut self) -> &mut CompressionInfo {
63+
&mut self.compression_info
64+
}
65+
}
66+
```
67+
68+
## Generated Instructions
69+
70+
### 1. Config Management
71+
72+
```typescript
73+
// Create config (only program upgrade authority can call)
74+
await program.methods
75+
.createCompressibleConfig(
76+
100, // compression_delay
77+
rentRecipient,
78+
[addressSpace] // Now accepts an array of address trees (1-4 allowed)
79+
)
80+
.accounts({
81+
payer: wallet.publicKey,
82+
config: configPda,
83+
programData: programDataPda,
84+
authority: upgradeAuthority,
85+
systemProgram: SystemProgram.programId,
86+
})
87+
.signers([upgradeAuthority])
88+
.rpc();
89+
90+
// Update config
91+
await program.methods
92+
.updateCompressibleConfig(
93+
200, // new_compression_delay (optional)
94+
newRentRecipient, // (optional)
95+
[newAddressSpace1, newAddressSpace2], // (optional) - array of 1-4 address trees
96+
newUpdateAuthority // (optional)
97+
)
98+
.accounts({
99+
config: configPda,
100+
authority: configUpdateAuthority,
101+
})
102+
.signers([configUpdateAuthority])
103+
.rpc();
104+
```
105+
106+
### 2. Compress Existing PDA
107+
108+
```typescript
109+
await program.methods
110+
.compressUserRecord(proof, compressedAccountMeta)
111+
.accounts({
112+
user: user.publicKey,
113+
pdaAccount: userRecordPda,
114+
systemProgram: SystemProgram.programId,
115+
config: configPda,
116+
rentRecipient: rentRecipient,
117+
})
118+
.remainingAccounts(lightSystemAccounts)
119+
.signers([user])
120+
.rpc();
121+
```
122+
123+
### 3. Decompress Multiple PDAs
124+
125+
```typescript
126+
const compressedAccounts = [
127+
{
128+
meta: compressedAccountMeta1,
129+
data: { userRecord: userData },
130+
seeds: [Buffer.from("user_record"), user.publicKey.toBuffer()],
131+
},
132+
{
133+
meta: compressedAccountMeta2,
134+
data: { gameSession: gameData },
135+
seeds: [
136+
Buffer.from("game_session"),
137+
sessionId.toArrayLike(Buffer, "le", 8),
138+
],
139+
},
140+
];
141+
142+
await program.methods
143+
.decompressMultiplePdas(
144+
proof,
145+
compressedAccounts,
146+
[userBump, gameBump], // PDA bumps
147+
systemAccountsOffset
148+
)
149+
.accounts({
150+
feePayer: payer.publicKey,
151+
rentPayer: payer.publicKey,
152+
systemProgram: SystemProgram.programId,
153+
})
154+
.remainingAccounts([
155+
...pdaAccounts, // PDAs to decompress into
156+
...lightSystemAccounts, // Light Protocol system accounts
157+
])
158+
.signers([payer])
159+
.rpc();
160+
```
161+
162+
## Address Space Configuration
163+
164+
The config now supports multiple address trees per address space (1-4 allowed):
165+
166+
```typescript
167+
// Single address tree (backward compatible)
168+
const addressSpace = [addressTree1];
169+
170+
// Multiple address trees for better scalability
171+
const addressSpace = [addressTree1, addressTree2, addressTree3];
172+
173+
// When creating config
174+
await program.methods
175+
.createCompressibleConfig(
176+
100,
177+
rentRecipient,
178+
addressSpace // Array of 1-4 unique address tree pubkeys
179+
)
180+
// ... accounts
181+
.rpc();
182+
```
183+
184+
### Address Space Validation Rules
185+
186+
**Create Config:**
187+
188+
- Must contain 1-4 unique address tree pubkeys
189+
- No duplicate pubkeys allowed
190+
- All pubkeys must be valid address trees
191+
192+
**Update Config:**
193+
194+
- Can only **add** new address trees, never remove existing ones
195+
- No duplicate pubkeys allowed in the new configuration
196+
- Must maintain all existing address trees
197+
198+
```typescript
199+
// Valid update: adding new trees
200+
const currentAddressSpace = [tree1, tree2];
201+
const newAddressSpace = [tree1, tree2, tree3]; // ✅ Valid: adds tree3
202+
203+
// Invalid update: removing existing trees
204+
const invalidAddressSpace = [tree2, tree3]; // ❌ Invalid: removes tree1
205+
```
206+
207+
The system validates that compressed accounts use address trees from the configured address space, providing flexibility while maintaining security and preventing accidental removal of active trees.
208+
209+
## What You Need to Implement
210+
211+
Since the macro only generates compression-related instructions, you need to implement:
212+
213+
### 1. Create Instructions
214+
215+
Implement your own create instructions for each account type:
216+
217+
```rust
218+
#[derive(Accounts)]
219+
pub struct CreateUserRecord<'info> {
220+
#[account(mut)]
221+
pub user: Signer<'info>,
222+
#[account(
223+
init,
224+
payer = user,
225+
space = 8 + UserRecord::INIT_SPACE,
226+
seeds = [b"user_record", user.key().as_ref()],
227+
bump,
228+
)]
229+
pub user_record: Account<'info, UserRecord>,
230+
pub system_program: Program<'info, System>,
231+
}
232+
233+
pub fn create_user_record(
234+
ctx: Context<CreateUserRecord>,
235+
name: String,
236+
) -> Result<()> {
237+
let user_record = &mut ctx.accounts.user_record;
238+
239+
// Your custom initialization logic here
240+
user_record.compression_info = CompressionInfo::new()?;
241+
user_record.owner = ctx.accounts.user.key();
242+
user_record.name = name;
243+
user_record.score = 0;
244+
245+
Ok(())
246+
}
247+
```
248+
249+
### 2. Update Instructions
250+
251+
Implement update instructions for your account types with your custom business logic.
252+
253+
## Customization
254+
255+
### Custom Seeds
256+
257+
Use custom seeds in your PDA derivation and pass them in the `seeds` parameter when decompressing:
258+
259+
```rust
260+
seeds = [b"custom_prefix", user.key().as_ref(), &session_id.to_le_bytes()]
261+
```
262+
263+
## Best Practices
264+
265+
1. **Create Config Early**: Create the config immediately after program deployment
266+
2. **Use Config Values**: Always use config values instead of hardcoded constants
267+
3. **Validate Rent Recipient**: The macro automatically validates rent recipient matches config
268+
4. **Handle Compression Timing**: Respect the compression delay from config
269+
5. **Batch Operations**: Use decompress_multiple_pdas for efficiency
270+
271+
## Migration from Manual Implementation
272+
273+
If migrating from a manual implementation:
274+
275+
1. Update your account structs to use `CompressionInfo` instead of separate fields
276+
2. Implement the `HasCompressionInfo` trait
277+
3. Replace your manual instructions with the macro
278+
4. Update client code to use the new instruction names
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)