Skip to content

Commit 2c0078d

Browse files
wip
1 parent 0e3e5f4 commit 2c0078d

File tree

12 files changed

+1373
-381
lines changed

12 files changed

+1373
-381
lines changed

Cargo.lock

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

program-tests/sdk-test-derived/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ default = []
2121
[dependencies]
2222
light-sdk = { workspace = true }
2323
light-sdk-types = { workspace = true }
24+
light-sdk-macros = { workspace = true }
2425
light-hasher = { workspace = true, features = ["solana"] }
2526
solana-program = { workspace = true }
2627
light-macros = { workspace = true, features = ["solana"] }
Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
# Native Solana Compressible Instructions Macro Usage
2+
3+
This example demonstrates how to use the `add_native_compressible_instructions` macro for native Solana programs with flexible instruction dispatching.
4+
5+
## Design Philosophy
6+
7+
The macro generates thin wrapper processor functions that developers dispatch manually. This provides:
8+
9+
- **Full control over instruction routing** - Use enums, constants, or any dispatch pattern
10+
- **Transparency** - developers see all available functions
11+
- **Flexibility** - Mix generated and custom instructions seamlessly
12+
- **Custom error handling** per instruction
13+
14+
## Basic Usage with Enum Dispatch (Recommended)
15+
16+
```rust
17+
use light_sdk_macros::add_native_compressible_instructions;
18+
use light_sdk::error::LightSdkError;
19+
use solana_program::{
20+
account_info::AccountInfo,
21+
entrypoint::ProgramResult,
22+
program_error::ProgramError,
23+
pubkey::Pubkey,
24+
};
25+
use borsh::BorshDeserialize;
26+
27+
// Define your account structs with required traits
28+
#[derive(Default, Clone, Debug, BorshSerialize, BorshDeserialize, LightHasher, LightDiscriminator)]
29+
pub struct MyPdaAccount {
30+
#[skip] // Skip compression_info in hashing
31+
pub compression_info: CompressionInfo,
32+
#[hash] // Hash pubkeys to field size
33+
pub owner: Pubkey,
34+
pub data: u64,
35+
}
36+
37+
// Implement required trait
38+
impl HasCompressionInfo for MyPdaAccount {
39+
fn compression_info(&self) -> &CompressionInfo {
40+
&self.compression_info
41+
}
42+
43+
fn compression_info_mut(&mut self) -> &mut CompressionInfo {
44+
&mut self.compression_info
45+
}
46+
}
47+
48+
// Generate compression processors
49+
#[add_native_compressible_instructions(MyPdaAccount)]
50+
pub mod compression {
51+
use super::*;
52+
}
53+
54+
// Define instruction enum (flexible - you choose the discriminators)
55+
#[repr(u8)]
56+
pub enum InstructionType {
57+
// Compression instructions (generated by macro)
58+
CreateCompressionConfig = 0,
59+
UpdateCompressionConfig = 1,
60+
DecompressMultiplePdas = 2,
61+
CompressMyPdaAccount = 3,
62+
63+
// Your custom instructions
64+
CreateMyPdaAccount = 20,
65+
UpdateMyPdaAccount = 21,
66+
}
67+
68+
impl TryFrom<u8> for InstructionType {
69+
type Error = LightSdkError;
70+
71+
fn try_from(value: u8) -> Result<Self, Self::Error> {
72+
match value {
73+
0 => Ok(InstructionType::CreateCompressionConfig),
74+
1 => Ok(InstructionType::UpdateCompressionConfig),
75+
2 => Ok(InstructionType::DecompressMultiplePdas),
76+
3 => Ok(InstructionType::CompressMyPdaAccount),
77+
20 => Ok(InstructionType::CreateMyPdaAccount),
78+
21 => Ok(InstructionType::UpdateMyPdaAccount),
79+
_ => Err(LightSdkError::ConstraintViolation),
80+
}
81+
}
82+
}
83+
84+
// Dispatch in your process_instruction
85+
pub fn process_instruction(
86+
program_id: &Pubkey,
87+
accounts: &[AccountInfo],
88+
instruction_data: &[u8],
89+
) -> ProgramResult {
90+
if instruction_data.is_empty() {
91+
return Err(ProgramError::InvalidInstructionData);
92+
}
93+
94+
let discriminator = InstructionType::try_from(instruction_data[0])
95+
.map_err(|_| ProgramError::InvalidInstructionData)?;
96+
let data = &instruction_data[1..];
97+
98+
match discriminator {
99+
InstructionType::CreateCompressionConfig => {
100+
let params = compression::CreateCompressionConfigData::try_from_slice(data)?;
101+
compression::create_compression_config(
102+
accounts,
103+
params.compression_delay,
104+
params.rent_recipient,
105+
params.address_space,
106+
)
107+
}
108+
InstructionType::CompressMyPdaAccount => {
109+
let params = compression::CompressMyPdaAccountData::try_from_slice(data)?;
110+
compression::compress_my_pda_account(
111+
accounts,
112+
params.proof,
113+
params.compressed_account_meta,
114+
)
115+
}
116+
InstructionType::CreateMyPdaAccount => {
117+
// Your custom create logic
118+
create_my_pda_account(accounts, data)
119+
}
120+
// ... other instructions
121+
}
122+
}
123+
```
124+
125+
## Alternative: Constants-based Dispatch
126+
127+
```rust
128+
// If you prefer constants (less type-safe but simpler)
129+
pub mod instruction {
130+
pub const CREATE_COMPRESSION_CONFIG: u8 = 0;
131+
pub const COMPRESS_MY_PDA_ACCOUNT: u8 = 3;
132+
pub const CREATE_MY_PDA_ACCOUNT: u8 = 20;
133+
}
134+
135+
pub fn process_instruction(
136+
program_id: &Pubkey,
137+
accounts: &[AccountInfo],
138+
instruction_data: &[u8],
139+
) -> ProgramResult {
140+
let discriminator = instruction_data[0];
141+
let data = &instruction_data[1..];
142+
143+
match discriminator {
144+
instruction::CREATE_COMPRESSION_CONFIG => {
145+
let params = compression::CreateCompressionConfigData::try_from_slice(data)?;
146+
compression::create_compression_config(/* ... */)
147+
}
148+
instruction::COMPRESS_MY_PDA_ACCOUNT => {
149+
let params = compression::CompressMyPdaAccountData::try_from_slice(data)?;
150+
compression::compress_my_pda_account(/* ... */)
151+
}
152+
instruction::CREATE_MY_PDA_ACCOUNT => {
153+
create_my_pda_account(accounts, data)
154+
}
155+
_ => Err(ProgramError::InvalidInstructionData),
156+
}
157+
}
158+
```
159+
160+
## Generated Types and Functions
161+
162+
The macro generates the following in your `compression` module:
163+
164+
### Data Structures
165+
166+
- `CompressedAccountVariant` - Enum of all compressible account types
167+
- `CompressedAccountData` - Wrapper for compressed account data with metadata
168+
- `CreateCompressionConfigData` - Instruction data for config creation
169+
- `UpdateCompressionConfigData` - Instruction data for config updates
170+
- `DecompressMultiplePdasData` - Instruction data for batch decompression
171+
- `Compress{AccountName}Data` - Instruction data for each account type
172+
173+
### Processor Functions
174+
175+
- `create_compression_config()` - Creates compression configuration
176+
- `update_compression_config()` - Updates compression configuration
177+
- `decompress_multiple_pdas()` - Decompresses multiple PDAs in one transaction
178+
- `compress_{account_name}()` - Compresses specific account type (snake_case)
179+
180+
## Account Layouts
181+
182+
Each processor function documents its expected account layout:
183+
184+
### create_compression_config
185+
186+
```
187+
0. [writable, signer] Payer account
188+
1. [writable] Config PDA (seeds: [b"compressible_config"])
189+
2. [] Program data account
190+
3. [signer] Program upgrade authority
191+
4. [] System program
192+
```
193+
194+
### compress\_{account_name}
195+
196+
```
197+
0. [signer] Authority
198+
1. [writable] PDA account to compress
199+
2. [] System program
200+
3. [] Config PDA
201+
4. [] Rent recipient (must match config)
202+
5... [] Light Protocol system accounts
203+
```
204+
205+
### decompress_multiple_pdas
206+
207+
```
208+
0. [writable, signer] Fee payer
209+
1. [writable, signer] Rent payer
210+
2. [] System program
211+
3..N. [writable] PDA accounts to decompress into
212+
N+1... [] Light Protocol system accounts
213+
```
214+
215+
## Multiple Account Types
216+
217+
```rust
218+
#[add_native_compressible_instructions(UserAccount, GameState, TokenVault)]
219+
pub mod compression {
220+
use super::*;
221+
}
222+
```
223+
224+
This generates compress functions for each type:
225+
226+
- `compress_user_account()`
227+
- `compress_game_state()`
228+
- `compress_token_vault()`
229+
230+
## Key Benefits
231+
232+
1. **Flexible Dispatch**: Choose enums, constants, or any pattern you prefer
233+
2. **Manual Control**: You decide which instructions to expose and how to route them
234+
3. **Custom Business Logic**: Easy to add custom create/update instructions alongside compression
235+
4. **Clear Account Requirements**: Each function documents its exact account layout
236+
5. **Type Safety**: Borsh serialization ensures type-safe instruction data
237+
6. **Zero Assumptions**: Macro doesn't impose any instruction routing patterns
238+
239+
## Client-Side Usage
240+
241+
```typescript
242+
// TypeScript/JavaScript client example
243+
import { Connection, PublicKey, TransactionInstruction } from "@solana/web3.js";
244+
import * as borsh from "borsh";
245+
246+
// Define instruction data schemas
247+
const CreateCompressionConfigSchema = borsh.struct([
248+
borsh.u32("compression_delay"),
249+
borsh.publicKey("rent_recipient"),
250+
borsh.vec(borsh.publicKey(), "address_space"),
251+
]);
252+
253+
// Build instruction with your chosen discriminator
254+
const instructionData = {
255+
compression_delay: 100,
256+
rent_recipient: rentRecipientPubkey,
257+
address_space: [addressTreePubkey],
258+
};
259+
260+
const serialized = borsh.serialize(
261+
CreateCompressionConfigSchema,
262+
instructionData
263+
);
264+
const instruction = new TransactionInstruction({
265+
keys: [
266+
/* account metas */
267+
],
268+
programId: PROGRAM_ID,
269+
data: Buffer.concat([
270+
Buffer.from([0]), // Your chosen discriminator for CreateCompressionConfig
271+
Buffer.from(serialized),
272+
]),
273+
});
274+
```
275+
276+
The macro provides maximum flexibility while automating the compression boilerplate, letting you focus on your program's unique business logic.

0 commit comments

Comments
 (0)