@layerzerolabs/framework-solana-anchor-trait 0.2.62 → 0.2.64
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 +3 -0
- package/README.md +316 -0
- package/examples/shapes/Cargo.lock +4126 -0
- package/examples/shapes/Cargo.toml +16 -0
- package/examples/shapes/macros/src/audit.rs +15 -0
- package/examples/shapes/macros/src/instructions/clear.rs +19 -0
- package/examples/shapes/macros/src/instructions/compute_area.rs +18 -20
- package/examples/shapes/macros/src/instructions/compute_perimeter.rs +18 -20
- package/examples/shapes/macros/src/instructions/get_dimension.rs +19 -0
- package/examples/shapes/macros/src/instructions/get_dimension_count.rs +19 -17
- package/examples/shapes/macros/src/instructions/mod.rs +8 -0
- package/examples/shapes/macros/src/instructions/reset.rs +50 -0
- package/examples/shapes/macros/src/instructions/scale.rs +52 -0
- package/examples/shapes/macros/src/lib.rs +14 -11
- package/examples/shapes/macros/src/shapes.rs +21 -26
- package/examples/shapes/macros/src/state_types.rs +14 -0
- package/examples/shapes/programs/ellipse/Cargo.toml +7 -0
- package/examples/shapes/programs/ellipse/src/lib.rs +69 -17
- package/examples/shapes/programs/ellipse/tests/ellipse/helpers.rs +110 -0
- package/examples/shapes/programs/ellipse/tests/ellipse/negative.rs +49 -0
- package/examples/shapes/programs/ellipse/tests/ellipse/positive.rs +231 -0
- package/examples/shapes/programs/ellipse/tests/ellipse.rs +23 -0
- package/examples/shapes/programs/triangle/Cargo.toml +7 -0
- package/examples/shapes/programs/triangle/src/lib.rs +69 -16
- package/examples/shapes/programs/triangle/tests/triangle/helpers.rs +123 -0
- package/examples/shapes/programs/triangle/tests/triangle/negative.rs +49 -0
- package/examples/shapes/programs/triangle/tests/triangle/positive.rs +225 -0
- package/examples/shapes/programs/triangle/tests/triangle.rs +23 -0
- package/examples/shapes/target/deploy/shapes_ellipse-keypair.json +1 -0
- package/examples/shapes/target/deploy/shapes_ellipse.so +0 -0
- package/examples/shapes/target/deploy/shapes_triangle-keypair.json +1 -0
- package/examples/shapes/target/deploy/shapes_triangle.so +0 -0
- package/package.json +20 -9
- package/src/assembly.rs +41 -6
- package/src/generation.rs +108 -131
- package/src/lib.rs +6 -4
- package/src/overrides.rs +56 -81
- package/tests/assembly.rs +154 -23
- package/tests/common/mod.rs +68 -14
- package/tests/generation.rs +137 -65
- package/tests/override.rs +93 -55
- package/tests/snapshots/snapshots__tests__snapshot_all_defaults.snap +32 -5
- package/tests/snapshots/snapshots__tests__snapshot_full_override.snap +35 -7
- package/tests/snapshots/snapshots__tests__snapshot_handler_override.snap +37 -7
- package/tests/snapshots/snapshots__tests__snapshot_mixed_overrides.snap +40 -9
- package/tests/snapshots/snapshots__tests__snapshot_with_extend_macro.snap +82 -0
- package/tests/snapshots.rs +51 -18
- package/examples/shapes/Anchor.toml +0 -19
- package/examples/shapes/programs/ellipse/src/state.rs +0 -9
- package/examples/shapes/programs/triangle/src/state.rs +0 -9
package/Cargo.toml
CHANGED
package/README.md
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
# Anchor Trait
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The Anchor framework provides no mechanism for defining or enforcing instruction-level interfaces across programs. When a standard requires a set of instructions — e.g., every OApp program must expose `set_peer` and `lz_receive` with specific signatures — there is no compile-time guarantee that a conforming program provides them all, uses the correct parameter types, or returns the expected types. Violations surface only at runtime, typically as CPI failures or deserialization errors.
|
|
6
|
+
|
|
7
|
+
`anchor-trait` introduces **instruction traits** to Anchor: compile-time enforceable instruction interfaces with optional default implementations. A trait author declares an instruction set — each instruction's parameter type, return type, and whether a default implementation is provided — and the framework ensures, during proc-macro expansion, that every implementing program satisfies the trait contract. Missing required instructions produce compile errors. View instructions additionally enforce **compile-time immutability**: handlers receive `&Context<T>` (not `&mut`), so read-only semantics are upheld by the type system rather than relying solely on Solana runtime protections.
|
|
8
|
+
|
|
9
|
+
The framework is domain-agnostic. It operates purely at the instruction-signature and accounts-struct level, with no knowledge of any specific application domain. Macro crates such as `lz-oapp-macros` or `rbac-macros` integrate with `anchor-trait` to define their own domain-specific instruction traits.
|
|
10
|
+
|
|
11
|
+
### Correspondence to Rust traits
|
|
12
|
+
|
|
13
|
+
| Rust trait | `anchor-trait` |
|
|
14
|
+
| ---------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
15
|
+
| Required method (no default body) | Instruction with `default_impl: None` — implementor must provide a handler |
|
|
16
|
+
| Default method (has body) | Instruction with `default_impl: Some(...)` — framework provides a complete implementation |
|
|
17
|
+
| `impl Trait for Type` | Applying the domain macro to a module (e.g., `#[oapp(...)] mod my_program { ... }`) |
|
|
18
|
+
| Overriding a default method | Annotating a handler with `#[oapp_instruction]` — the handler's `Context<T>` determines the accounts type |
|
|
19
|
+
| Compile error on unimplemented required method | Compile error on missing required instruction handler |
|
|
20
|
+
|
|
21
|
+
## Instruction Kinds
|
|
22
|
+
|
|
23
|
+
Each instruction in a trait is classified as one of two kinds:
|
|
24
|
+
|
|
25
|
+
**Required** — The trait provides no default implementation. The implementor must supply a handler annotated with the override attribute; omitting it produces a compile error. This is analogous to a required method in a Rust trait.
|
|
26
|
+
|
|
27
|
+
```rust
|
|
28
|
+
InstructionSpec {
|
|
29
|
+
params_type: Some(quote!(LzReceiveParams)),
|
|
30
|
+
return_type: quote!(()),
|
|
31
|
+
is_view: false,
|
|
32
|
+
default_impl: None,
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Provided** — The trait supplies a complete default implementation, including an accounts struct and a handler function. The default accounts struct is always emitted, so it remains available even when the implementor overrides the handler. This is analogous to a default method in a Rust trait.
|
|
37
|
+
|
|
38
|
+
```rust
|
|
39
|
+
InstructionSpec {
|
|
40
|
+
params_type: Some(quote!(SetPeerParams)),
|
|
41
|
+
return_type: quote!(()),
|
|
42
|
+
is_view: false,
|
|
43
|
+
default_impl: Some(DefaultImpl {
|
|
44
|
+
handler_ident: format_ident!("apply"),
|
|
45
|
+
generator: generate_default_impl,
|
|
46
|
+
}),
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Overriding Instructions
|
|
51
|
+
|
|
52
|
+
For provided instructions, the implementor may accept the default or supply a custom handler annotated with the override attribute (e.g., `#[oapp_instruction]`). The handler's `Context<T>` type determines the entrypoint's accounts type:
|
|
53
|
+
|
|
54
|
+
- To **reuse** the default accounts struct, declare `Context<StdSetPeer>`.
|
|
55
|
+
- To use **custom** accounts, declare `Context<MyCustomAccounts>`.
|
|
56
|
+
|
|
57
|
+
The framework does not distinguish between these cases — the default struct is always emitted regardless.
|
|
58
|
+
|
|
59
|
+
For required instructions, no default exists. The implementor must provide a handler.
|
|
60
|
+
|
|
61
|
+
### Resolution algorithm
|
|
62
|
+
|
|
63
|
+
1. **Handler present** → dispatch to the handler (entrypoint uses the handler's declared context type)
|
|
64
|
+
2. **No handler, default exists** → dispatch to the default handler
|
|
65
|
+
3. **No handler, no default** → compile error
|
|
66
|
+
|
|
67
|
+
Unannotated functions whose identifiers collide with managed instruction names are rejected at compile time. Such functions must be annotated with the override attribute (e.g. `#[oapp_instruction]`) to disambiguate intent.
|
|
68
|
+
|
|
69
|
+
## Defining a Trait
|
|
70
|
+
|
|
71
|
+
The following steps describe how to create a new instruction trait using the framework.
|
|
72
|
+
|
|
73
|
+
### 1. Define a codegen context
|
|
74
|
+
|
|
75
|
+
The codegen context is a struct that carries domain-specific resolved types and is threaded through every instruction generator:
|
|
76
|
+
|
|
77
|
+
```rust
|
|
78
|
+
pub(crate) struct CodegenContext<'a> {
|
|
79
|
+
pub role_type: &'a TokenStream,
|
|
80
|
+
pub state_type: &'a TokenStream,
|
|
81
|
+
// additional domain-specific fields
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 2. Declare the instruction set
|
|
86
|
+
|
|
87
|
+
The `declare_instructions!` macro enumerates all instructions in the trait. It generates:
|
|
88
|
+
|
|
89
|
+
- Sub-module declarations (`mod set_peer;`, `mod get_peer;`)
|
|
90
|
+
- A named enum implementing `InstructionSet` with `all()`, `from_ident()`, `canonical_name()`, and `spec()`
|
|
91
|
+
|
|
92
|
+
```rust
|
|
93
|
+
use anchor_trait::declare_instructions;
|
|
94
|
+
|
|
95
|
+
declare_instructions! {
|
|
96
|
+
name = OAppInstructionSet;
|
|
97
|
+
context = CodegenContext;
|
|
98
|
+
|
|
99
|
+
SetPeer => set_peer,
|
|
100
|
+
GetPeer => get_peer,
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 3. Define per-instruction specs
|
|
105
|
+
|
|
106
|
+
Each sub-module exports a `spec()` function returning an `InstructionSpec<CodegenContext>` that describes the instruction's signature and default implementation status:
|
|
107
|
+
|
|
108
|
+
```rust
|
|
109
|
+
// Provided instruction — implementor may accept the default or override:
|
|
110
|
+
pub(super) fn spec<'a>() -> InstructionSpec<CodegenContext<'a>> {
|
|
111
|
+
InstructionSpec {
|
|
112
|
+
params_type: Some(quote!(::my_crate::params::SetPeerParams)),
|
|
113
|
+
return_type: quote!(()),
|
|
114
|
+
is_view: false,
|
|
115
|
+
default_impl: Some(DefaultImpl {
|
|
116
|
+
handler_ident: format_ident!("apply"),
|
|
117
|
+
generator: generate_default_impl,
|
|
118
|
+
}),
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
fn generate_default_impl(
|
|
123
|
+
ctx: &CodegenContext<'_>,
|
|
124
|
+
spec: &InstructionSpec<CodegenContext<'_>>,
|
|
125
|
+
) -> DefaultAccounts {
|
|
126
|
+
let params_type = spec.params_type.as_ref().unwrap();
|
|
127
|
+
let handler_ident = &spec.default_impl.as_ref().unwrap().handler_ident;
|
|
128
|
+
|
|
129
|
+
// Return complete struct + impl — the framework emits as-is and decomposes for the extend arm
|
|
130
|
+
DefaultAccounts {
|
|
131
|
+
struct_type: parse_quote! {
|
|
132
|
+
#[event_cpi]
|
|
133
|
+
#[derive(Accounts)]
|
|
134
|
+
#[instruction(params: #params_type)]
|
|
135
|
+
pub struct StdSetPeer<'info> {
|
|
136
|
+
pub authority: Signer<'info>,
|
|
137
|
+
// ... additional fields ...
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
impl_block: parse_quote! {
|
|
141
|
+
impl StdSetPeer<'_> {
|
|
142
|
+
pub fn #handler_ident(ctx: &mut Context<Self>, params: &#params_type) -> Result<()> {
|
|
143
|
+
// ... default handler logic ...
|
|
144
|
+
Ok(())
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Required instruction — implementor must provide a handler:
|
|
152
|
+
pub(super) fn spec<'a>() -> InstructionSpec<CodegenContext<'a>> {
|
|
153
|
+
InstructionSpec {
|
|
154
|
+
params_type: Some(quote!(::my_crate::params::LzReceiveParams)),
|
|
155
|
+
return_type: quote!(()),
|
|
156
|
+
is_view: false,
|
|
157
|
+
default_impl: None,
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### 4. Configure the override attribute
|
|
163
|
+
|
|
164
|
+
```rust
|
|
165
|
+
const OVERRIDE_CONFIG: OverrideConfig = OverrideConfig {
|
|
166
|
+
attr_name: "oapp_instruction", // attribute written by implementors
|
|
167
|
+
domain: "OApp", // label used in diagnostics
|
|
168
|
+
};
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### 5. Implement the macro entry point
|
|
172
|
+
|
|
173
|
+
The entry point follows a four-phase pipeline: **parse → collect → generate → assemble**.
|
|
174
|
+
|
|
175
|
+
```rust
|
|
176
|
+
pub fn my_macro_impl(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
|
|
177
|
+
// Phase 0: Parse inputs (domain-specific; outside the framework's scope)
|
|
178
|
+
let attr_args = NestedMeta::parse_meta_list(attr)?;
|
|
179
|
+
let attrs = MyDomainAttr::from_list(&attr_args)?;
|
|
180
|
+
let module = syn::parse2::<ItemMod>(item)?;
|
|
181
|
+
let codegen_ctx = /* resolve domain types from attrs */;
|
|
182
|
+
|
|
183
|
+
// Phase 1: Collect — scan for override handlers
|
|
184
|
+
let overrides = InstructionOverrides::collect(&module, OVERRIDE_CONFIG)?;
|
|
185
|
+
|
|
186
|
+
// Phase 2: Generate — emit instruction code
|
|
187
|
+
let instructions = generate_instructions::<MyInstructionSet>(&codegen_ctx, &overrides)?;
|
|
188
|
+
|
|
189
|
+
// Phase 3: Assemble — combine all generated fragments into the final output
|
|
190
|
+
Ok(Assembly {
|
|
191
|
+
prelude: /* domain-specific definitions */,
|
|
192
|
+
overrides,
|
|
193
|
+
instructions,
|
|
194
|
+
extra_mod_attrs: /* e.g. quote!(#[program]) */,
|
|
195
|
+
source_mod: module,
|
|
196
|
+
extend_macro_name: Some(format_ident!("my_domain_extend_accounts")),
|
|
197
|
+
}
|
|
198
|
+
.assemble())
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Assembly output structure
|
|
203
|
+
|
|
204
|
+
```text
|
|
205
|
+
<prelude> — domain-specific definitions (emitted by the macro crate)
|
|
206
|
+
<wrapper_defs> — relocated override handler modules
|
|
207
|
+
<default_impls> — generated default accounts structs + handler impls
|
|
208
|
+
<extend_macro> — macro_rules! for extending default accounts (if configured)
|
|
209
|
+
<original_mod_attrs> — preserved module attributes
|
|
210
|
+
<extra_mod_attrs> — additional attributes (e.g., #[program])
|
|
211
|
+
[vis] mod <name> {
|
|
212
|
+
use super::*;
|
|
213
|
+
<filtered_items> — original module items, excluding relocated handlers
|
|
214
|
+
<entrypoints> — generated instruction entrypoints
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Extending Default Accounts
|
|
219
|
+
|
|
220
|
+
For provided instructions, the framework generates a `macro_rules!` (named via `Assembly::extend_macro_name`) that lets users create extended accounts structs — the default fields plus extra accounts — without duplicating the base implementation.
|
|
221
|
+
|
|
222
|
+
### Usage
|
|
223
|
+
|
|
224
|
+
The extend macro must be invoked **after** the domain macro module in the source file:
|
|
225
|
+
|
|
226
|
+
```rust
|
|
227
|
+
#[my_domain(state = MyState)]
|
|
228
|
+
pub mod my_program {
|
|
229
|
+
#[my_domain_instruction]
|
|
230
|
+
pub fn set_peer(ctx: &mut Context<ExtSetPeer>, params: &SetPeerParams) -> Result<()> {
|
|
231
|
+
ExtSetPeer::apply(ctx, params)?; // default handler, copied to extended struct
|
|
232
|
+
ctx.accounts.my_extra.do_something()?; // extra account
|
|
233
|
+
Ok(())
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Must appear after the domain macro module (macro_rules! is textually scoped).
|
|
238
|
+
// Rust item name resolution is NOT order-dependent, so ExtSetPeer is visible above.
|
|
239
|
+
my_domain_extend_accounts!(set_peer, ExtSetPeer, {
|
|
240
|
+
pub my_extra: Account<'info, MyExtraState>,
|
|
241
|
+
});
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### How it works
|
|
245
|
+
|
|
246
|
+
Each provided instruction's `DefaultAccounts` produces two outputs: the concrete `Std*` struct (via `to_definition()`, which emits the `ItemStruct` and `ItemImpl` as-is) and a `macro_rules!` arm (via `to_extend_rule()`, which decomposes the struct into attrs/fields, replaces the name with `$name`, and appends `$($extra)*`). `Assembly` collects all arms into one `macro_rules!` named by `extend_macro_name`.
|
|
247
|
+
|
|
248
|
+
## Reference
|
|
249
|
+
|
|
250
|
+
### Naming conventions
|
|
251
|
+
|
|
252
|
+
| Item | Convention | Example |
|
|
253
|
+
| ----------------------- | ---------------------------- | --------------------------------- |
|
|
254
|
+
| Default accounts struct | `Std<PascalCaseInstruction>` | `StdSetPeer` |
|
|
255
|
+
| Override attribute | `<domain>_instruction` | `oapp_instruction` |
|
|
256
|
+
| Params type | Fully qualified path | `::lz_oapp::types::SetPeerParams` |
|
|
257
|
+
|
|
258
|
+
### InstructionSpec fields
|
|
259
|
+
|
|
260
|
+
| Field | Type | Description |
|
|
261
|
+
| -------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
262
|
+
| `params_type` | `Option<TokenStream>` | Parameter type for the generated entrypoint. Must be a single struct type (e.g., `SetPeerParams`); multiple parameters are not supported. `None` indicates the instruction accepts no parameters. |
|
|
263
|
+
| `return_type` | `TokenStream` | Return type of the generated entrypoint (the `T` in `Result<T>`). |
|
|
264
|
+
| `is_view` | `bool` | When `true`, the entrypoint receives `ctx: Context<T>` (immutable) and passes `&ctx` to the handler. When `false`, it receives `mut ctx: Context<T>` and passes `&mut ctx`. |
|
|
265
|
+
| `default_impl` | `Option<DefaultImpl<Ctx>>` | `Some(...)` designates a provided instruction with a default implementation. `None` designates a required instruction; the implementor must supply a handler. |
|
|
266
|
+
|
|
267
|
+
### DefaultImpl fields
|
|
268
|
+
|
|
269
|
+
| Field | Type | Description |
|
|
270
|
+
| --------------- | ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
271
|
+
| `handler_ident` | `Ident` | Identifier of the default handler method (e.g., `apply`). When no override is present, the entrypoint invokes `<accounts_ident>::<handler_ident>(...)`. |
|
|
272
|
+
| `generator` | `fn(&Ctx, &InstructionSpec<Ctx>) -> DefaultAccounts` | Returns a `DefaultAccounts` containing a complete `ItemStruct` and `ItemImpl`. The framework emits them directly for the concrete definition and decomposes them for the extend `macro_rules!` arm. |
|
|
273
|
+
|
|
274
|
+
### DefaultAccounts fields
|
|
275
|
+
|
|
276
|
+
| Field | Type | Description |
|
|
277
|
+
| ------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
278
|
+
| `struct_type` | `ItemStruct` | A complete accounts struct definition including attributes, name, generics, and fields. Written with `parse_quote!` so generators read as plain Rust. The struct name becomes the `accounts_ident` (e.g. `StdSetPeer`). |
|
|
279
|
+
| `impl_block` | `ItemImpl` | A complete `impl` block for the struct, containing handler methods. Written with `parse_quote!` so generators read as plain Rust. Handler methods must use `Context<Self>` so they work for both the concrete struct and any extended variant. |
|
|
280
|
+
|
|
281
|
+
### Default accounts struct
|
|
282
|
+
|
|
283
|
+
Each `DefaultAccounts` returned by a generator must satisfy the following:
|
|
284
|
+
|
|
285
|
+
- `struct_type` must be a named-field struct with a `<'info>` lifetime parameter (the standard Anchor accounts lifetime)
|
|
286
|
+
- `struct_type` must carry the standard Anchor attributes (e.g. `#[derive(Accounts)]`) — these are preserved as-is in `to_definition()` and re-emitted by `to_extend_rule()`
|
|
287
|
+
- `impl_block` must define a handler method matching `handler_ident` with one of the following signatures:
|
|
288
|
+
- Mutable: `fn apply(ctx: &mut Context<Self>, params: &ParamsType) -> Result<ReturnType>`
|
|
289
|
+
- View: `fn apply(ctx: &Context<Self>, params: &ParamsType) -> Result<ReturnType>`
|
|
290
|
+
- Omit the `params` parameter when `params_type` is `None`
|
|
291
|
+
- Handler methods must use `Context<Self>` (not a concrete struct name) so they work for both the concrete `Std*` struct and any extended variant
|
|
292
|
+
- The struct is emitted **outside** the `#[program]` module
|
|
293
|
+
|
|
294
|
+
### Entrypoint signatures
|
|
295
|
+
|
|
296
|
+
```rust
|
|
297
|
+
// Mutable instruction with parameters:
|
|
298
|
+
pub fn set_peer(mut ctx: Context<CtxType>, params: ParamsType) -> Result<ReturnType> { ... }
|
|
299
|
+
|
|
300
|
+
// View instruction with parameters:
|
|
301
|
+
pub fn get_peer(ctx: Context<CtxType>, params: ParamsType) -> Result<ReturnType> { ... }
|
|
302
|
+
|
|
303
|
+
// Instruction without parameters:
|
|
304
|
+
pub fn set_delegate(mut ctx: Context<CtxType>) -> Result<()> { ... }
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Dispatch
|
|
308
|
+
|
|
309
|
+
| Scenario | `CtxType` | Dispatch (with params) | Dispatch (no params) |
|
|
310
|
+
| ---------------- | ---------------------- | ----------------------------------------- | -------------------------------- |
|
|
311
|
+
| No handler | `StdSetPeer` | `StdSetPeer::apply(&mut ctx, ¶ms)` | `StdSetPeer::apply(&mut ctx)` |
|
|
312
|
+
| Handler provided | handler's declared `T` | `__wrapper__::handler(&mut ctx, ¶ms)` | `__wrapper__::handler(&mut ctx)` |
|
|
313
|
+
|
|
314
|
+
- All handlers receive `ctx` by reference (`&mut` for mutable, `&` for views) and `params` by reference. `__wrapper__` denotes the relocated wrapper module (e.g., `__oapp_instruction__set_peer_handler`).
|
|
315
|
+
- For view instructions, `&mut` becomes `&` in the dispatch expressions above.
|
|
316
|
+
- Override handlers that pass `ctx` by value (`Context<T>` instead of `&Context<T>` or `&mut Context<T>`) are rejected at compile time.
|