@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.
Files changed (34) hide show
  1. package/Cargo.toml +18 -0
  2. package/examples/shapes/Anchor.toml +19 -0
  3. package/examples/shapes/macros/Cargo.toml +17 -0
  4. package/examples/shapes/macros/src/instructions/compute_area.rs +50 -0
  5. package/examples/shapes/macros/src/instructions/compute_perimeter.rs +50 -0
  6. package/examples/shapes/macros/src/instructions/get_dimension_count.rs +44 -0
  7. package/examples/shapes/macros/src/instructions/get_name.rs +18 -0
  8. package/examples/shapes/macros/src/instructions/initialize.rs +18 -0
  9. package/examples/shapes/macros/src/instructions/mod.rs +26 -0
  10. package/examples/shapes/macros/src/lib.rs +50 -0
  11. package/examples/shapes/macros/src/shapes.rs +56 -0
  12. package/examples/shapes/programs/ellipse/Cargo.toml +30 -0
  13. package/examples/shapes/programs/ellipse/src/lib.rs +88 -0
  14. package/examples/shapes/programs/ellipse/src/state.rs +9 -0
  15. package/examples/shapes/programs/triangle/Cargo.toml +30 -0
  16. package/examples/shapes/programs/triangle/src/lib.rs +83 -0
  17. package/examples/shapes/programs/triangle/src/state.rs +9 -0
  18. package/package.json +36 -0
  19. package/rust-toolchain.toml +3 -0
  20. package/rustfmt.toml +23 -0
  21. package/src/assembly.rs +63 -0
  22. package/src/generation.rs +226 -0
  23. package/src/lib.rs +126 -0
  24. package/src/overrides.rs +289 -0
  25. package/tests/assembly.rs +339 -0
  26. package/tests/common/mod.rs +206 -0
  27. package/tests/declare_instructions.rs +206 -0
  28. package/tests/generation.rs +358 -0
  29. package/tests/override.rs +414 -0
  30. package/tests/snapshots/snapshots__tests__snapshot_all_defaults.snap +35 -0
  31. package/tests/snapshots/snapshots__tests__snapshot_full_override.snap +47 -0
  32. package/tests/snapshots/snapshots__tests__snapshot_handler_override.snap +42 -0
  33. package/tests/snapshots/snapshots__tests__snapshot_mixed_overrides.snap +57 -0
  34. 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
+ }