@layerzerolabs/framework-solana-anchor-trait 0.2.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +18 -0
- package/examples/shapes/Anchor.toml +19 -0
- package/examples/shapes/macros/Cargo.toml +17 -0
- package/examples/shapes/macros/src/instructions/compute_area.rs +50 -0
- package/examples/shapes/macros/src/instructions/compute_perimeter.rs +50 -0
- package/examples/shapes/macros/src/instructions/get_dimension_count.rs +44 -0
- package/examples/shapes/macros/src/instructions/get_name.rs +18 -0
- package/examples/shapes/macros/src/instructions/initialize.rs +18 -0
- package/examples/shapes/macros/src/instructions/mod.rs +26 -0
- package/examples/shapes/macros/src/lib.rs +50 -0
- package/examples/shapes/macros/src/shapes.rs +56 -0
- package/examples/shapes/programs/ellipse/Cargo.toml +30 -0
- package/examples/shapes/programs/ellipse/src/lib.rs +88 -0
- package/examples/shapes/programs/ellipse/src/state.rs +9 -0
- package/examples/shapes/programs/triangle/Cargo.toml +30 -0
- package/examples/shapes/programs/triangle/src/lib.rs +83 -0
- package/examples/shapes/programs/triangle/src/state.rs +9 -0
- package/package.json +36 -0
- package/rust-toolchain.toml +3 -0
- package/rustfmt.toml +23 -0
- package/src/assembly.rs +63 -0
- package/src/generation.rs +226 -0
- package/src/lib.rs +126 -0
- package/src/overrides.rs +289 -0
- package/tests/assembly.rs +339 -0
- package/tests/common/mod.rs +206 -0
- package/tests/declare_instructions.rs +206 -0
- package/tests/generation.rs +358 -0
- package/tests/override.rs +414 -0
- package/tests/snapshots/snapshots__tests__snapshot_all_defaults.snap +35 -0
- package/tests/snapshots/snapshots__tests__snapshot_full_override.snap +47 -0
- package/tests/snapshots/snapshots__tests__snapshot_handler_override.snap +42 -0
- package/tests/snapshots/snapshots__tests__snapshot_mixed_overrides.snap +57 -0
- package/tests/snapshots.rs +139 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
mod common;
|
|
2
|
+
|
|
3
|
+
#[cfg(test)]
|
|
4
|
+
mod tests {
|
|
5
|
+
use super::common::*;
|
|
6
|
+
use quote::ToTokens;
|
|
7
|
+
|
|
8
|
+
// -----------------------------------------------------------------------
|
|
9
|
+
// Tests — override level resolution
|
|
10
|
+
// -----------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
#[test]
|
|
13
|
+
fn default_level_emits_context_and_entrypoint() {
|
|
14
|
+
// All defaults — no overrides except required lz_receive
|
|
15
|
+
let module: syn::ItemMod = syn::parse_quote! {
|
|
16
|
+
mod my_program {
|
|
17
|
+
#[test_instruction]
|
|
18
|
+
pub fn lz_receive(ctx: Context<MyReceive>, params: LzReceiveParams) -> Result<()> {
|
|
19
|
+
Ok(())
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
let gen = generate_ok(&module);
|
|
24
|
+
|
|
25
|
+
// Default impls should be generated for each provided instruction
|
|
26
|
+
let structs = parse_structs(&gen.default_impls);
|
|
27
|
+
assert!(find_struct(&structs, "StdSetPeer").is_some(), "should emit StdSetPeer");
|
|
28
|
+
assert!(find_struct(&structs, "StdGetPeer").is_some(), "should emit StdGetPeer");
|
|
29
|
+
assert!(find_struct(&structs, "StdToken").is_some(), "should emit StdToken");
|
|
30
|
+
|
|
31
|
+
// Entrypoints should include all instructions
|
|
32
|
+
let fns = parse_fns(&gen.entrypoints);
|
|
33
|
+
assert!(find_fn(&fns, "set_peer").is_some(), "should have set_peer entrypoint");
|
|
34
|
+
assert!(find_fn(&fns, "get_peer").is_some(), "should have get_peer entrypoint");
|
|
35
|
+
assert!(find_fn(&fns, "lz_receive").is_some(), "should have lz_receive entrypoint");
|
|
36
|
+
assert!(find_fn(&fns, "token").is_some(), "should have token entrypoint");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
#[test]
|
|
40
|
+
fn default_mutable_passes_mut_ref() {
|
|
41
|
+
let module: syn::ItemMod = syn::parse_quote! {
|
|
42
|
+
mod my_program {
|
|
43
|
+
#[test_instruction]
|
|
44
|
+
pub fn lz_receive(ctx: Context<MyReceive>, params: LzReceiveParams) -> Result<()> {
|
|
45
|
+
Ok(())
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
let gen = generate_ok(&module);
|
|
50
|
+
let fns = parse_fns(&gen.entrypoints);
|
|
51
|
+
let set_peer = find_fn(&fns, "set_peer").expect("set_peer entrypoint should exist");
|
|
52
|
+
|
|
53
|
+
// set_peer is mutable (is_view = false), default level → mut ctx: Context<StdSetPeer>
|
|
54
|
+
let (is_mut, ctx_type) = first_param_context_type(set_peer);
|
|
55
|
+
assert!(is_mut, "set_peer should have `mut ctx`");
|
|
56
|
+
assert_eq!(ctx_type, "StdSetPeer", "should use default context type");
|
|
57
|
+
|
|
58
|
+
// Default handler called by reference: StdSetPeer::apply(&mut ctx, ...)
|
|
59
|
+
let body = set_peer.block.to_token_stream().to_string();
|
|
60
|
+
assert!(
|
|
61
|
+
body.contains("StdSetPeer :: apply (& mut ctx"),
|
|
62
|
+
"mutable default should pass &mut ctx to handler: {body}"
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
#[test]
|
|
67
|
+
fn default_view_passes_immutable_ref() {
|
|
68
|
+
let module: syn::ItemMod = syn::parse_quote! {
|
|
69
|
+
mod my_program {
|
|
70
|
+
#[test_instruction]
|
|
71
|
+
pub fn lz_receive(ctx: Context<MyReceive>, params: LzReceiveParams) -> Result<()> {
|
|
72
|
+
Ok(())
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
let gen = generate_ok(&module);
|
|
77
|
+
let fns = parse_fns(&gen.entrypoints);
|
|
78
|
+
let get_peer = find_fn(&fns, "get_peer").expect("get_peer entrypoint should exist");
|
|
79
|
+
|
|
80
|
+
// get_peer is view (is_view = true), default level → ctx: Context<StdGetPeer>
|
|
81
|
+
let (is_mut, ctx_type) = first_param_context_type(get_peer);
|
|
82
|
+
assert!(!is_mut, "view default should use `ctx` (no mut)");
|
|
83
|
+
assert_eq!(ctx_type, "StdGetPeer", "should use default context type");
|
|
84
|
+
|
|
85
|
+
// Default handler called by immutable reference: StdGetPeer::apply(&ctx, ...)
|
|
86
|
+
let body = get_peer.block.to_token_stream().to_string();
|
|
87
|
+
assert!(
|
|
88
|
+
body.contains("StdGetPeer :: apply (& ctx"),
|
|
89
|
+
"view default should pass &ctx to handler: {body}"
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
#[test]
|
|
94
|
+
fn handler_override_emits_default_context() {
|
|
95
|
+
// Override set_peer but reuse StdSetPeer context
|
|
96
|
+
let module: syn::ItemMod = syn::parse_quote! {
|
|
97
|
+
mod my_program {
|
|
98
|
+
#[test_instruction]
|
|
99
|
+
pub fn set_peer(ctx: Context<StdSetPeer>, params: SetPeerParams) -> Result<()> {
|
|
100
|
+
Ok(())
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
#[test_instruction]
|
|
104
|
+
pub fn lz_receive(ctx: Context<MyReceive>, params: LzReceiveParams) -> Result<()> {
|
|
105
|
+
Ok(())
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
let gen = generate_ok(&module);
|
|
110
|
+
|
|
111
|
+
// Default impl should still be generated (handler reuses it)
|
|
112
|
+
let structs = parse_structs(&gen.default_impls);
|
|
113
|
+
assert!(
|
|
114
|
+
find_struct(&structs, "StdSetPeer").is_some(),
|
|
115
|
+
"handler override should still emit StdSetPeer context"
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// Entrypoint should dispatch to wrapper
|
|
119
|
+
let fns = parse_fns(&gen.entrypoints);
|
|
120
|
+
let set_peer = find_fn(&fns, "set_peer").expect("set_peer entrypoint should exist");
|
|
121
|
+
let body = set_peer.block.to_token_stream().to_string();
|
|
122
|
+
assert!(
|
|
123
|
+
body.contains("__test_instruction__set_peer_handler :: set_peer"),
|
|
124
|
+
"should dispatch to wrapper handler: {body}"
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
#[test]
|
|
129
|
+
fn full_override_skips_default_context() {
|
|
130
|
+
// Override set_peer with a custom context type (not StdSetPeer)
|
|
131
|
+
let module: syn::ItemMod = syn::parse_quote! {
|
|
132
|
+
mod my_program {
|
|
133
|
+
#[test_instruction]
|
|
134
|
+
pub fn set_peer(ctx: Context<MyCustomAccounts>, params: SetPeerParams) -> Result<()> {
|
|
135
|
+
Ok(())
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
#[test_instruction]
|
|
139
|
+
pub fn lz_receive(ctx: Context<MyReceive>, params: LzReceiveParams) -> Result<()> {
|
|
140
|
+
Ok(())
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
let gen = generate_ok(&module);
|
|
145
|
+
|
|
146
|
+
// Full override should NOT emit default impl for set_peer.
|
|
147
|
+
// Other provided instructions (get_peer, token) still get their defaults.
|
|
148
|
+
let structs = parse_structs(&gen.default_impls);
|
|
149
|
+
assert!(
|
|
150
|
+
find_struct(&structs, "StdSetPeer").is_none(),
|
|
151
|
+
"full override should skip default impl for set_peer"
|
|
152
|
+
);
|
|
153
|
+
assert!(
|
|
154
|
+
find_struct(&structs, "StdGetPeer").is_some(),
|
|
155
|
+
"get_peer should still have default"
|
|
156
|
+
);
|
|
157
|
+
assert!(find_struct(&structs, "StdToken").is_some(), "token should still have default");
|
|
158
|
+
|
|
159
|
+
// Entrypoint should use the custom context type
|
|
160
|
+
let fns = parse_fns(&gen.entrypoints);
|
|
161
|
+
let set_peer = find_fn(&fns, "set_peer").expect("set_peer entrypoint should exist");
|
|
162
|
+
let (is_mut, ctx_type) = first_param_context_type(set_peer);
|
|
163
|
+
assert!(is_mut, "set_peer should have `mut ctx`");
|
|
164
|
+
assert_eq!(ctx_type, "MyCustomAccounts", "full override should use custom context type");
|
|
165
|
+
|
|
166
|
+
// Should dispatch to wrapper
|
|
167
|
+
let body = set_peer.block.to_token_stream().to_string();
|
|
168
|
+
assert!(
|
|
169
|
+
body.contains("__test_instruction__set_peer_handler :: set_peer"),
|
|
170
|
+
"should dispatch to wrapper handler: {body}"
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
#[test]
|
|
175
|
+
fn override_receives_params_by_value() {
|
|
176
|
+
let module: syn::ItemMod = syn::parse_quote! {
|
|
177
|
+
mod my_program {
|
|
178
|
+
#[test_instruction]
|
|
179
|
+
pub fn set_peer(ctx: Context<MyAccounts>, params: SetPeerParams) -> Result<()> {
|
|
180
|
+
Ok(())
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
#[test_instruction]
|
|
184
|
+
pub fn lz_receive(ctx: Context<MyReceive>, params: LzReceiveParams) -> Result<()> {
|
|
185
|
+
Ok(())
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
let gen = generate_ok(&module);
|
|
190
|
+
let fns = parse_fns(&gen.entrypoints);
|
|
191
|
+
let set_peer = find_fn(&fns, "set_peer").expect("set_peer entrypoint should exist");
|
|
192
|
+
|
|
193
|
+
// Override handlers get (ctx, params) by value — no & or &mut on the call
|
|
194
|
+
let body = set_peer.block.to_token_stream().to_string();
|
|
195
|
+
assert!(
|
|
196
|
+
body.contains("set_peer (ctx , params)"),
|
|
197
|
+
"override should pass ctx and params by value: {body}"
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
#[test]
|
|
202
|
+
fn no_params_instruction_generates_parameterless_entrypoint() {
|
|
203
|
+
let module: syn::ItemMod = syn::parse_quote! {
|
|
204
|
+
mod my_program {
|
|
205
|
+
#[test_instruction]
|
|
206
|
+
pub fn lz_receive(ctx: Context<MyReceive>, params: LzReceiveParams) -> Result<()> {
|
|
207
|
+
Ok(())
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
let gen = generate_ok(&module);
|
|
212
|
+
let fns = parse_fns(&gen.entrypoints);
|
|
213
|
+
let token = find_fn(&fns, "token").expect("token entrypoint should exist");
|
|
214
|
+
|
|
215
|
+
// Token instruction has no params — entrypoint should only have ctx
|
|
216
|
+
assert_eq!(token.sig.inputs.len(), 1, "no-params instruction should have only ctx");
|
|
217
|
+
let (is_mut, ctx_type) = first_param_context_type(token);
|
|
218
|
+
assert!(!is_mut, "token is a view — should not be mut");
|
|
219
|
+
assert_eq!(ctx_type, "StdToken", "should use default context type");
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// -----------------------------------------------------------------------
|
|
223
|
+
// Tests — boundary conditions
|
|
224
|
+
// -----------------------------------------------------------------------
|
|
225
|
+
|
|
226
|
+
#[test]
|
|
227
|
+
fn view_override_with_params_gets_immutable_ctx() {
|
|
228
|
+
// Override get_peer (view, has params) — entrypoint should have
|
|
229
|
+
// immutable ctx and dispatch to wrapper with (ctx, params) by value.
|
|
230
|
+
let module: syn::ItemMod = syn::parse_quote! {
|
|
231
|
+
mod my_program {
|
|
232
|
+
#[test_instruction]
|
|
233
|
+
pub fn get_peer(ctx: Context<MyGetPeerAccounts>, params: GetPeerParams) -> Result<[u8; 32]> {
|
|
234
|
+
Ok([0u8; 32])
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
#[test_instruction]
|
|
238
|
+
pub fn lz_receive(ctx: Context<MyReceive>, params: LzReceiveParams) -> Result<()> {
|
|
239
|
+
Ok(())
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
let gen = generate_ok(&module);
|
|
244
|
+
let fns = parse_fns(&gen.entrypoints);
|
|
245
|
+
let get_peer = find_fn(&fns, "get_peer").expect("get_peer entrypoint should exist");
|
|
246
|
+
|
|
247
|
+
// View override: ctx should NOT be mut
|
|
248
|
+
let (is_mut, ctx_type) = first_param_context_type(get_peer);
|
|
249
|
+
assert!(!is_mut, "view override should have immutable ctx");
|
|
250
|
+
assert_eq!(ctx_type, "MyGetPeerAccounts", "should use override's context type");
|
|
251
|
+
|
|
252
|
+
// Dispatches to wrapper by value
|
|
253
|
+
let body = get_peer.block.to_token_stream().to_string();
|
|
254
|
+
assert!(
|
|
255
|
+
body.contains("get_peer (ctx , params)"),
|
|
256
|
+
"view override should pass ctx and params by value: {body}"
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
#[test]
|
|
261
|
+
fn override_no_params_receives_ctx_by_value() {
|
|
262
|
+
// Override the token instruction (no params) — entrypoint should
|
|
263
|
+
// dispatch to wrapper with just `ctx` by value.
|
|
264
|
+
let module: syn::ItemMod = syn::parse_quote! {
|
|
265
|
+
mod my_program {
|
|
266
|
+
#[test_instruction]
|
|
267
|
+
pub fn token(ctx: Context<MyTokenAccounts>) -> Result<Pubkey> {
|
|
268
|
+
Ok(Pubkey::default())
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
#[test_instruction]
|
|
272
|
+
pub fn lz_receive(ctx: Context<MyReceive>, params: LzReceiveParams) -> Result<()> {
|
|
273
|
+
Ok(())
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
let gen = generate_ok(&module);
|
|
278
|
+
let fns = parse_fns(&gen.entrypoints);
|
|
279
|
+
let token = find_fn(&fns, "token").expect("token entrypoint should exist");
|
|
280
|
+
|
|
281
|
+
// No-params override: entrypoint has only ctx, dispatches wrapper::handler(ctx)
|
|
282
|
+
assert_eq!(token.sig.inputs.len(), 1, "no-params override should have only ctx");
|
|
283
|
+
let body = token.block.to_token_stream().to_string();
|
|
284
|
+
assert!(
|
|
285
|
+
body.contains("token (ctx)"),
|
|
286
|
+
"no-params override should pass ctx by value: {body}"
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
#[test]
|
|
291
|
+
fn return_type_from_spec_propagated_to_entrypoint() {
|
|
292
|
+
let module: syn::ItemMod = syn::parse_quote! {
|
|
293
|
+
mod my_program {
|
|
294
|
+
#[test_instruction]
|
|
295
|
+
pub fn lz_receive(ctx: Context<MyReceive>, params: LzReceiveParams) -> Result<()> {
|
|
296
|
+
Ok(())
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
let gen = generate_ok(&module);
|
|
301
|
+
let fns = parse_fns(&gen.entrypoints);
|
|
302
|
+
|
|
303
|
+
// get_peer returns [u8; 32] per spec
|
|
304
|
+
let get_peer = find_fn(&fns, "get_peer").expect("get_peer should exist");
|
|
305
|
+
let ret = get_peer.sig.output.to_token_stream().to_string();
|
|
306
|
+
assert!(ret.contains("[u8 ; 32]"), "get_peer return type should be [u8; 32]: {ret}");
|
|
307
|
+
|
|
308
|
+
// token returns Pubkey per spec
|
|
309
|
+
let token = find_fn(&fns, "token").expect("token should exist");
|
|
310
|
+
let ret = token.sig.output.to_token_stream().to_string();
|
|
311
|
+
assert!(ret.contains("Pubkey"), "token return type should be Pubkey: {ret}");
|
|
312
|
+
|
|
313
|
+
// set_peer returns () per spec
|
|
314
|
+
let set_peer = find_fn(&fns, "set_peer").expect("set_peer should exist");
|
|
315
|
+
let ret = set_peer.sig.output.to_token_stream().to_string();
|
|
316
|
+
assert!(ret.contains("()"), "set_peer return type should be (): {ret}");
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
#[test]
|
|
320
|
+
fn fn_generics_from_handler_propagated_to_entrypoint() {
|
|
321
|
+
// Override with lifetime generics — entrypoint should carry them.
|
|
322
|
+
let module: syn::ItemMod = syn::parse_quote! {
|
|
323
|
+
mod my_program {
|
|
324
|
+
#[test_instruction]
|
|
325
|
+
pub fn lz_receive<'info>(
|
|
326
|
+
ctx: Context<'_, '_, '_, 'info, MyReceive<'info>>,
|
|
327
|
+
params: LzReceiveParams,
|
|
328
|
+
) -> Result<()> {
|
|
329
|
+
Ok(())
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
let gen = generate_ok(&module);
|
|
334
|
+
let fns = parse_fns(&gen.entrypoints);
|
|
335
|
+
let lz_receive = find_fn(&fns, "lz_receive").expect("lz_receive should exist");
|
|
336
|
+
|
|
337
|
+
let generics = lz_receive.sig.generics.to_token_stream().to_string();
|
|
338
|
+
assert!(
|
|
339
|
+
generics.contains("'info"),
|
|
340
|
+
"entrypoint should carry handler's lifetime generics: {generics}"
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// -----------------------------------------------------------------------
|
|
345
|
+
// Tests — signature validation errors
|
|
346
|
+
// -----------------------------------------------------------------------
|
|
347
|
+
|
|
348
|
+
#[test]
|
|
349
|
+
fn error_required_instruction_missing_handler() {
|
|
350
|
+
// lz_receive is required (no default) but not overridden
|
|
351
|
+
let module: syn::ItemMod = syn::parse_quote! {
|
|
352
|
+
mod my_program {}
|
|
353
|
+
};
|
|
354
|
+
let err = generate_err(&module);
|
|
355
|
+
assert!(err.contains("lz_receive"), "should mention instruction name: {err}");
|
|
356
|
+
assert!(err.contains("no default implementation"), "should explain why: {err}");
|
|
357
|
+
}
|
|
358
|
+
}
|