@5ive-tech/cli 1.0.15 → 1.0.17
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/dist/assets/vm/five_vm_wasm_bg.wasm +0 -0
- package/package.json +1 -1
- package/templates/AGENTS.md +729 -104
|
Binary file
|
package/package.json
CHANGED
package/templates/AGENTS.md
CHANGED
|
@@ -31,69 +31,17 @@ Never rely on stale docs when behavior is high-stakes (deploy/execute/CPI encodi
|
|
|
31
31
|
5. Execute and verify confirmed tx metadata (`meta.err == null`).
|
|
32
32
|
6. Record signatures + compute units.
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
Use this profile unless you have direct proof in your current compiler version that a broader syntax feature is stable.
|
|
37
|
-
|
|
38
|
-
1. Prefer top-level accounts + top-level functions. Do not wrap programs in `script Name { ... }`.
|
|
39
|
-
2. Use account/signer/mut attributes in this order style:
|
|
40
|
-
- `owner: pubkey @signer`
|
|
41
|
-
- `state: Mint @mut`
|
|
42
|
-
- `ata: TokenAccount @mut @init`
|
|
43
|
-
3. Prefer plain function declarations like:
|
|
44
|
-
- `pub transfer(...) { ... }`
|
|
45
|
-
- `pub mint_to(...) { ... }`
|
|
46
|
-
4. Any function intended to be called via `5ive execute` must be declared `pub`.
|
|
47
|
-
5. Keep logic to `require`, `if/else`, arithmetic, assignments, and simple returns.
|
|
48
|
-
6. Validate after every meaningful edit:
|
|
49
|
-
- `5ive compile src/main.v -o build/main.five`
|
|
50
|
-
|
|
51
|
-
Avoid these patterns by default (high-risk for invalid output in agent-generated code):
|
|
52
|
-
1. `instruction ...` function form
|
|
53
|
-
2. Attribute-first parameters like `@signer owner: pubkey`
|
|
54
|
-
3. Optional parameters/fields in signatures (`x?: T`) and nullish expressions (`??`)
|
|
55
|
-
4. `enum`/`event` heavy first-pass contracts
|
|
56
|
-
5. Event emission (`event` / `emit`) unless you have compile+runtime proof in your current toolchain
|
|
57
|
-
6. Unverified literals/helpers like `pubkey(0)` unless confirmed in working examples
|
|
58
|
-
7. Parser-only/experimental syntax from tokenizer docs without a compile-verified example
|
|
59
|
-
|
|
60
|
-
Safe token-style baseline:
|
|
61
|
-
```v
|
|
62
|
-
account Mint {
|
|
63
|
-
authority: pubkey;
|
|
64
|
-
supply: u64;
|
|
65
|
-
decimals: u8;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
account TokenAccount {
|
|
69
|
-
owner_key: pubkey;
|
|
70
|
-
mint: pubkey;
|
|
71
|
-
bal: u64;
|
|
72
|
-
}
|
|
34
|
+
### 3.1 Strict Authoring Rules
|
|
73
35
|
|
|
74
|
-
|
|
75
|
-
state: Mint @mut,
|
|
76
|
-
authority: pubkey @signer,
|
|
77
|
-
decimals: u8
|
|
78
|
-
) {
|
|
79
|
-
state.authority = authority;
|
|
80
|
-
state.supply = 0;
|
|
81
|
-
state.decimals = decimals;
|
|
82
|
-
}
|
|
36
|
+
These rules are non-negotiable and prevent the most common compilation failures:
|
|
83
37
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
require(source.mint == destination.mint, "mint mismatch");
|
|
92
|
-
require(source.bal >= amount, "insufficient funds");
|
|
93
|
-
source.bal = source.bal - amount;
|
|
94
|
-
destination.bal = destination.bal + amount;
|
|
95
|
-
}
|
|
96
|
-
```
|
|
38
|
+
1. **All account fields must end with `;`** — missing semicolons cause parser failure.
|
|
39
|
+
2. **Use `account @signer` for authorization** — not `pubkey @signer`. This preserves the `.key` accessor.
|
|
40
|
+
3. **Use `.key` on `account`-typed parameters** to extract public keys for comparisons and assignments.
|
|
41
|
+
4. **Use `-> ReturnType` for functions with return values** — e.g. `-> u64`, `-> pubkey`, `-> bool`.
|
|
42
|
+
5. **`pubkey(0)` and `0` are valid** for zero-initializing pubkey fields (disabling authorities).
|
|
43
|
+
6. **`string<N>` is production-safe** — use freely in accounts and function parameters.
|
|
44
|
+
7. **All comparison operators work in `require()`** — `==`, `!=`, `<`, `<=`, `>`, `>=`, `!`.
|
|
97
45
|
|
|
98
46
|
## 4) DSL Feature Inventory (Deep)
|
|
99
47
|
|
|
@@ -101,7 +49,7 @@ This section enumerates language features discovered from parser/compiler code a
|
|
|
101
49
|
|
|
102
50
|
### 4.1 Top-level declarations
|
|
103
51
|
Observed and/or parsed:
|
|
104
|
-
1. `account Name { ... }`
|
|
52
|
+
1. `account Name { ... }` — **all fields must be terminated with `;`**
|
|
105
53
|
2. Global fields/variables (including `mut`)
|
|
106
54
|
3. `init { ... }` block
|
|
107
55
|
4. `constraints { ... }` block
|
|
@@ -111,6 +59,21 @@ Observed and/or parsed:
|
|
|
111
59
|
8. `use` / `import` statements
|
|
112
60
|
9. Legacy `script Name { ... }` wrapper (parser-supported)
|
|
113
61
|
|
|
62
|
+
```v
|
|
63
|
+
// ✅ CORRECT — semicolons required
|
|
64
|
+
account Mint {
|
|
65
|
+
authority: pubkey;
|
|
66
|
+
supply: u64;
|
|
67
|
+
decimals: u8;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ❌ WRONG — parser failure
|
|
71
|
+
account Mint {
|
|
72
|
+
authority: pubkey
|
|
73
|
+
supply: u64
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
114
77
|
### 4.2 Function definition forms
|
|
115
78
|
Parser accepts flexible forms:
|
|
116
79
|
1. `pub add(...) -> ... { ... }`
|
|
@@ -141,11 +104,28 @@ Common attributes:
|
|
|
141
104
|
|
|
142
105
|
Examples also show legacy bracket seed forms after `@init`.
|
|
143
106
|
|
|
107
|
+
**Attribute stacking order for account parameters (empirically verified):**
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
Type @mut @init(payer=name, space=bytes) @signer
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Example:
|
|
114
|
+
```v
|
|
115
|
+
pub init_mint(
|
|
116
|
+
mint_account: Mint @mut @init(payer=authority, space=256) @signer,
|
|
117
|
+
authority: account @mut @signer,
|
|
118
|
+
...
|
|
119
|
+
)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Order: (1) Type declaration → (2) `@mut` → (3) `@init(...)` → (4) `@signer`.
|
|
123
|
+
|
|
144
124
|
### 4.5 Types
|
|
145
125
|
Supported/parsed type families:
|
|
146
126
|
1. Primitive numeric/bool/pubkey/string types (`u8..u128`, `i8..i64`, `bool`, `pubkey`, `string`)
|
|
147
127
|
2. `Account` type and account-typed params
|
|
148
|
-
3. Sized strings: `string<
|
|
128
|
+
3. **Sized strings: `string<N>`** — production-safe, use in accounts and function parameters
|
|
149
129
|
4. Arrays:
|
|
150
130
|
- Rust style: `[T; N]`
|
|
151
131
|
- TypeScript-style sized: `T[N]`
|
|
@@ -158,20 +138,29 @@ Supported/parsed type families:
|
|
|
158
138
|
- nested generics (`Option<Option<u64>>` etc.)
|
|
159
139
|
8. Namespaced/custom types: `module::Type`
|
|
160
140
|
9. Optional account fields in structs/accounts: `field?: Type`
|
|
141
|
+
10. **`pubkey(0)` and integer `0`** — valid for zero-initialization of pubkey fields (interchangeable)
|
|
161
142
|
|
|
162
143
|
### 4.6 Statements
|
|
163
144
|
Observed and parser-supported:
|
|
164
145
|
1. `let` declarations (with `mut` and optional type annotation)
|
|
146
|
+
- Type inference works: `let is_owner = source.owner == authority.key;` infers `bool`
|
|
147
|
+
- Use `let` without explicit annotation for boolean and scalar expressions
|
|
165
148
|
2. Assignment:
|
|
166
149
|
- direct: `x = y`
|
|
167
150
|
- compound: `+=`, `-=`, `*=`, `/=`, `<<=`, `>>=`, `&=`, `|=`, `^=`
|
|
168
151
|
3. Field assignment: `obj.field = value`
|
|
169
|
-
4. Return statements (`return`, `return value`)
|
|
170
|
-
5. Guard/assertion: `require(condition)`
|
|
152
|
+
4. Return statements (`return`, `return value`) — see §4.13 for return type syntax
|
|
153
|
+
5. Guard/assertion: `require(condition)` — **all operators verified:**
|
|
154
|
+
- Comparison: `==`, `!=`, `<`, `<=`, `>`, `>=`
|
|
155
|
+
- Boolean negation: `!expr`
|
|
156
|
+
- Logical: `&&`, `||`
|
|
157
|
+
- Example: `require(source.balance >= amount);`
|
|
158
|
+
- Example: `require(!account.is_frozen);`
|
|
171
159
|
6. Conditionals:
|
|
172
160
|
- `if (...) {}`
|
|
173
161
|
- `else if (...) {}`
|
|
174
162
|
- `else {}`
|
|
163
|
+
- Conditionals support nested `require()` statements and multiple assignments in both branches
|
|
175
164
|
7. Pattern matching: `match expr { ... }`, with optional arm guards (`if ...`)
|
|
176
165
|
8. Loops:
|
|
177
166
|
- `while (...) { ... }`
|
|
@@ -221,27 +210,69 @@ Parser handles:
|
|
|
221
210
|
- list: `::{a, b}`
|
|
222
211
|
- typed list entries: `method foo`, `interface Bar`
|
|
223
212
|
|
|
224
|
-
### 4.9 Interfaces and CPI
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
-
|
|
232
|
-
- `@serializer(
|
|
233
|
-
|
|
234
|
-
-
|
|
235
|
-
|
|
236
|
-
- `@discriminator(
|
|
237
|
-
|
|
238
|
-
- `
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
213
|
+
### 4.9 Interfaces and CPI (Cross-Program Invocation)
|
|
214
|
+
|
|
215
|
+
Interfaces define external program calls. **Empirically verified rules:**
|
|
216
|
+
|
|
217
|
+
1. **Program binding:** always use `@program("...")` (the `@` prefix is required)
|
|
218
|
+
2. **Serializer options:**
|
|
219
|
+
- **Default (bincode):** omit `@serializer(...)` — bincode is the default, works for SPL programs and most Solana programs
|
|
220
|
+
- **Anchor programs (borsh):** use `@anchor` marker — automatically sets borsh serializer **and** auto-generates discriminators from method names
|
|
221
|
+
- **Explicit borsh:** use `@serializer("borsh")` if needed without `@anchor`
|
|
222
|
+
3. **Discriminators:**
|
|
223
|
+
- **Manual:** use single `u8` value inline on method: `method @discriminator(N) (...)`
|
|
224
|
+
- **Anchor auto-generation:** `@anchor` interface automatically computes discriminators from method names — **do not** manually specify `@discriminator` with `@anchor`
|
|
225
|
+
- **Format:** single u8 value, **not** array format `@discriminator([3, 0, 0, 0])`
|
|
226
|
+
4. **Account parameters in interfaces:** use `Account` type, **not** `pubkey`
|
|
227
|
+
- `pubkey` is for data values only; `Account` represents an on-chain account passed to the CPI
|
|
228
|
+
5. **Calling interface methods:** use dot notation `InterfaceName.method(...)`, **not** `InterfaceName::method(...)`
|
|
229
|
+
6. **Passing accounts to CPI:** pass `account`-typed parameters directly, **not** `param.key`
|
|
230
|
+
7. **Function parameters for CPI accounts:** must be typed `account @mut` (not `pubkey`)
|
|
231
|
+
|
|
232
|
+
```v
|
|
233
|
+
// ✅ CORRECT: SPL Token (bincode, manual discriminators)
|
|
234
|
+
interface SPLToken @program("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA") {
|
|
235
|
+
transfer @discriminator(3) (
|
|
236
|
+
source: Account,
|
|
237
|
+
destination: Account,
|
|
238
|
+
authority: Account,
|
|
239
|
+
amount: u64
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ✅ CORRECT: Anchor program (borsh, auto discriminators)
|
|
244
|
+
interface MyAnchorProgram @anchor @program("...") {
|
|
245
|
+
initialize( // discriminator auto-generated from "initialize"
|
|
246
|
+
state: Account,
|
|
247
|
+
authority: Account,
|
|
248
|
+
value: u64
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ✅ CORRECT: CPI call
|
|
253
|
+
pub call_external(
|
|
254
|
+
external_account: account @mut,
|
|
255
|
+
authority: account @signer,
|
|
256
|
+
value: u64
|
|
257
|
+
) {
|
|
258
|
+
MyAnchorProgram.initialize(external_account, authority, value);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// ❌ WRONG — common mistakes
|
|
262
|
+
// interface Program program("...") ← missing @ on program
|
|
263
|
+
// @discriminator([3, 0, 0, 0]) ← array format, not u8
|
|
264
|
+
// transfer(src: pubkey, dst: pubkey, ...) ← pubkey instead of Account
|
|
265
|
+
// Program::method(...) ← :: instead of .
|
|
266
|
+
// Program.method(account.key, ...) ← .key unnecessary for accounts
|
|
267
|
+
// @anchor with @discriminator(3) ← @anchor auto-generates, don't specify manually
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
CPI hard rules for agents:
|
|
271
|
+
1. Always use `@program("...")` with correct program ID
|
|
272
|
+
2. For Anchor programs: use `@anchor`, omit `@discriminator`
|
|
273
|
+
3. For non-Anchor programs: set `@discriminator(N)` as single u8 on each method, omit `@serializer`
|
|
274
|
+
4. Use `Account` for on-chain account params, scalar types for data params
|
|
275
|
+
5. Call with dot notation and pass account params directly
|
|
245
276
|
|
|
246
277
|
### 4.10 Events and error/enums
|
|
247
278
|
Parser/AST include:
|
|
@@ -268,24 +299,64 @@ From tokenizer/parser support:
|
|
|
268
299
|
|
|
269
300
|
Repository tests also use comment-based param conventions (`// @test-params ...`) in many scripts.
|
|
270
301
|
|
|
271
|
-
### 4.12 Blockchain-oriented built-ins
|
|
272
|
-
|
|
302
|
+
### 4.12 Blockchain-oriented built-ins
|
|
303
|
+
Core built-ins available in all contracts:
|
|
273
304
|
1. `derive_pda(...)` (including bump-return and bump-specified variants)
|
|
274
305
|
2. `get_clock()`
|
|
275
306
|
3. `get_key(...)`
|
|
276
|
-
4.
|
|
307
|
+
4. **Account key access: `param.key`** — **core pattern for all `account`-typed parameters.** Use `.key` to extract pubkeys for comparisons and assignments.
|
|
308
|
+
|
|
309
|
+
```v
|
|
310
|
+
pub action(
|
|
311
|
+
state: MyAccount @mut,
|
|
312
|
+
caller: account @signer,
|
|
313
|
+
...
|
|
314
|
+
) {
|
|
315
|
+
require(state.authority == caller.key); // ownership check
|
|
316
|
+
state.last_actor = caller.key; // record who acted
|
|
317
|
+
}
|
|
318
|
+
```
|
|
277
319
|
|
|
278
|
-
|
|
320
|
+
5. **Authority revocation pattern:** assign `0` to any pubkey field to permanently disable it.
|
|
321
|
+
```v
|
|
322
|
+
state.authority = 0; // revokes authority — irreversible
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### 4.13 Return types and values
|
|
326
|
+
Functions can declare return types with `->` syntax:
|
|
327
|
+
|
|
328
|
+
```v
|
|
329
|
+
pub get_value(state: MyAccount) -> u64 {
|
|
330
|
+
return state.amount;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
pub initialize(
|
|
334
|
+
state: MyAccount @mut @init(payer=creator, space=256) @signer,
|
|
335
|
+
creator: account @mut @signer,
|
|
336
|
+
...
|
|
337
|
+
) -> pubkey {
|
|
338
|
+
state.authority = creator.key;
|
|
339
|
+
return state.key;
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
Confirmed return types: `u8`, `u16`, `u32`, `u64`, `u128`, `i8`..`i64`, `bool`, `pubkey`.
|
|
279
344
|
|
|
280
345
|
## 5) Feature Maturity Matrix (Agent Safety)
|
|
281
346
|
|
|
282
347
|
### 5.1 Generally production-oriented (widely used in templates)
|
|
283
|
-
1. Accounts, `@mut`, `@signer`, `@init`
|
|
284
|
-
2. `require`
|
|
285
|
-
3. Basic control flow (`if`, `while`)
|
|
348
|
+
1. Accounts, `@mut`, `@signer`, `@init` (with attribute stacking)
|
|
349
|
+
2. `require` with all comparison operators (`==`, `!=`, `<`, `<=`, `>`, `>=`, `!`)
|
|
350
|
+
3. Basic control flow (`if`, `else`, `while`) with nested logic
|
|
286
351
|
4. Arithmetic/comparison/boolean expressions
|
|
287
352
|
5. `.five` compile/deploy/execute path
|
|
288
353
|
6. `interface` + explicit discriminator + explicit serializer CPI patterns
|
|
354
|
+
7. Return type declarations (`-> Type`) with `return value;`
|
|
355
|
+
8. `string<N>` fixed-size strings in accounts and parameters
|
|
356
|
+
9. `account.key` extraction from `account`-typed parameters
|
|
357
|
+
10. Authority disabling via `0` assignment to pubkey fields
|
|
358
|
+
11. `let` with type inference for scalar/boolean expressions
|
|
359
|
+
12. `pubkey(0)` zero-initialization
|
|
289
360
|
|
|
290
361
|
### 5.2 Available but validate per-version before critical use
|
|
291
362
|
1. Match expressions with `Option`/`Result`
|
|
@@ -406,18 +477,248 @@ const program = FiveProgram.fromABI("<SCRIPT_ACCOUNT>", abi, {
|
|
|
406
477
|
3. Surface signatures, CU metrics, and rich error states.
|
|
407
478
|
4. Use LSP-backed editing where available to reduce DSL mistakes.
|
|
408
479
|
|
|
409
|
-
## 10) Pattern
|
|
480
|
+
## 10) Contract Pattern Recipes
|
|
481
|
+
|
|
482
|
+
This section provides composable patterns for the most common contract archetypes. When building a novel contract, identify which patterns apply and combine them.
|
|
483
|
+
|
|
484
|
+
### 10.1 Authority-Gated State (Vault, Treasury, Config)
|
|
485
|
+
|
|
486
|
+
Core pattern: one or more pubkey fields control who can mutate state. Used in almost every contract.
|
|
487
|
+
|
|
488
|
+
```v
|
|
489
|
+
account Config {
|
|
490
|
+
authority: pubkey;
|
|
491
|
+
value: u64;
|
|
492
|
+
is_locked: bool;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
pub update_value(
|
|
496
|
+
config: Config @mut,
|
|
497
|
+
authority: account @signer,
|
|
498
|
+
new_value: u64
|
|
499
|
+
) {
|
|
500
|
+
require(config.authority == authority.key);
|
|
501
|
+
require(!config.is_locked);
|
|
502
|
+
config.value = new_value;
|
|
503
|
+
}
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
**Key ingredients:** ownership check via `.key`, boolean guard with `!`, field mutation.
|
|
507
|
+
|
|
508
|
+
### 10.2 Custody & Withdraw (Vault, Staking)
|
|
509
|
+
|
|
510
|
+
Core pattern: deposit into an account, enforce balance invariants on withdraw.
|
|
511
|
+
|
|
512
|
+
```v
|
|
513
|
+
pub deposit(
|
|
514
|
+
vault: VaultAccount @mut,
|
|
515
|
+
depositor: account @signer,
|
|
516
|
+
amount: u64
|
|
517
|
+
) {
|
|
518
|
+
require(amount > 0);
|
|
519
|
+
vault.balance = vault.balance + amount;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
pub withdraw(
|
|
523
|
+
vault: VaultAccount @mut,
|
|
524
|
+
authority: account @signer,
|
|
525
|
+
amount: u64
|
|
526
|
+
) {
|
|
527
|
+
require(vault.authority == authority.key);
|
|
528
|
+
require(vault.balance >= amount);
|
|
529
|
+
require(amount > 0);
|
|
530
|
+
vault.balance = vault.balance - amount;
|
|
531
|
+
}
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
**Key ingredients:** `>= amount` balance guard, arithmetic on fields, `> 0` zero-amount prevention.
|
|
535
|
+
|
|
536
|
+
### 10.3 Lifecycle State Machine (Escrow, Auction, Proposal)
|
|
537
|
+
|
|
538
|
+
Core pattern: a status field controls which operations are valid. Transitions are guarded.
|
|
539
|
+
|
|
540
|
+
```v
|
|
541
|
+
account Escrow {
|
|
542
|
+
seller: pubkey;
|
|
543
|
+
buyer: pubkey;
|
|
544
|
+
amount: u64;
|
|
545
|
+
status: u8; // 0=open, 1=funded, 2=released, 3=cancelled
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
pub fund_escrow(
|
|
549
|
+
escrow: Escrow @mut,
|
|
550
|
+
buyer: account @signer,
|
|
551
|
+
amount: u64
|
|
552
|
+
) {
|
|
553
|
+
require(escrow.buyer == buyer.key);
|
|
554
|
+
require(escrow.status == 0);
|
|
555
|
+
require(amount == escrow.amount);
|
|
556
|
+
escrow.status = 1;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
pub release_escrow(
|
|
560
|
+
escrow: Escrow @mut,
|
|
561
|
+
buyer: account @signer
|
|
562
|
+
) {
|
|
563
|
+
require(escrow.buyer == buyer.key);
|
|
564
|
+
require(escrow.status == 1);
|
|
565
|
+
escrow.status = 2;
|
|
566
|
+
}
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
**Key ingredients:** integer status field with `==` checks for state transitions, dual-party authorization.
|
|
570
|
+
|
|
571
|
+
### 10.4 Supply Accounting (Token, Mint, Points)
|
|
572
|
+
|
|
573
|
+
Core pattern: a central supply counter stays synchronized with distributed balances.
|
|
574
|
+
|
|
575
|
+
```v
|
|
576
|
+
pub mint_to(
|
|
577
|
+
supply_state: SupplyAccount @mut,
|
|
578
|
+
destination: BalanceAccount @mut,
|
|
579
|
+
authority: account @signer,
|
|
580
|
+
amount: u64
|
|
581
|
+
) {
|
|
582
|
+
require(supply_state.authority == authority.key);
|
|
583
|
+
require(amount > 0);
|
|
584
|
+
supply_state.total_supply = supply_state.total_supply + amount;
|
|
585
|
+
destination.balance = destination.balance + amount;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
pub burn(
|
|
589
|
+
supply_state: SupplyAccount @mut,
|
|
590
|
+
source: BalanceAccount @mut,
|
|
591
|
+
owner: account @signer,
|
|
592
|
+
amount: u64
|
|
593
|
+
) {
|
|
594
|
+
require(source.owner == owner.key);
|
|
595
|
+
require(source.balance >= amount);
|
|
596
|
+
source.balance = source.balance - amount;
|
|
597
|
+
supply_state.total_supply = supply_state.total_supply - amount;
|
|
598
|
+
}
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
**Key ingredients:** paired increment/decrement across two accounts, conservation invariant.
|
|
602
|
+
|
|
603
|
+
### 10.5 Delegation & Approval (Token, DAO, Proxy)
|
|
604
|
+
|
|
605
|
+
Core pattern: an owner grants limited permissions to a delegate.
|
|
606
|
+
|
|
607
|
+
```v
|
|
608
|
+
pub approve(
|
|
609
|
+
state: DelegableAccount @mut,
|
|
610
|
+
owner: account @signer,
|
|
611
|
+
delegate: pubkey,
|
|
612
|
+
limit: u64
|
|
613
|
+
) {
|
|
614
|
+
require(state.owner == owner.key);
|
|
615
|
+
state.delegate = delegate;
|
|
616
|
+
state.delegated_limit = limit;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
pub revoke(
|
|
620
|
+
state: DelegableAccount @mut,
|
|
621
|
+
owner: account @signer
|
|
622
|
+
) {
|
|
623
|
+
require(state.owner == owner.key);
|
|
624
|
+
state.delegate = 0;
|
|
625
|
+
state.delegated_limit = 0;
|
|
626
|
+
}
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
**Key ingredients:** delegate pubkey field, limit tracking, zero-assignment to revoke.
|
|
630
|
+
|
|
631
|
+
### 10.6 Conservation Math (AMM, Orderbook, Settlement)
|
|
632
|
+
|
|
633
|
+
Core pattern: total value across accounts must remain constant.
|
|
634
|
+
|
|
635
|
+
```v
|
|
636
|
+
pub swap(
|
|
637
|
+
pool_a: PoolAccount @mut,
|
|
638
|
+
pool_b: PoolAccount @mut,
|
|
639
|
+
user_a: UserAccount @mut,
|
|
640
|
+
user_b: UserAccount @mut,
|
|
641
|
+
trader: account @signer,
|
|
642
|
+
amount_in: u64
|
|
643
|
+
) {
|
|
644
|
+
require(user_a.owner == trader.key);
|
|
645
|
+
require(user_a.balance >= amount_in);
|
|
646
|
+
require(amount_in > 0);
|
|
647
|
+
let amount_out = (pool_b.reserve * amount_in) / (pool_a.reserve + amount_in);
|
|
648
|
+
require(amount_out > 0);
|
|
649
|
+
user_a.balance = user_a.balance - amount_in;
|
|
650
|
+
pool_a.reserve = pool_a.reserve + amount_in;
|
|
651
|
+
pool_b.reserve = pool_b.reserve - amount_out;
|
|
652
|
+
user_b.balance = user_b.balance + amount_out;
|
|
653
|
+
}
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
**Key ingredients:** `let` with computed expression, multi-account mutation, balance checks on both sides.
|
|
657
|
+
|
|
658
|
+
### 10.7 Threshold & Risk Checks (Lending, Collateral, Liquidation)
|
|
659
|
+
|
|
660
|
+
Core pattern: actions gated by ratio or threshold comparisons.
|
|
661
|
+
|
|
662
|
+
```v
|
|
663
|
+
pub borrow(
|
|
664
|
+
position: LoanPosition @mut,
|
|
665
|
+
borrower: account @signer,
|
|
666
|
+
collateral_value: u64,
|
|
667
|
+
borrow_amount: u64
|
|
668
|
+
) {
|
|
669
|
+
require(position.owner == borrower.key);
|
|
670
|
+
require(borrow_amount > 0);
|
|
671
|
+
// Enforce 150% collateral ratio: collateral * 100 >= total_debt * 150
|
|
672
|
+
let new_debt = position.debt + borrow_amount;
|
|
673
|
+
require(collateral_value * 100 >= new_debt * 150);
|
|
674
|
+
position.debt = new_debt;
|
|
675
|
+
}
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
**Key ingredients:** `let` for intermediate computation, integer math for ratio checks, compound conditions.
|
|
679
|
+
|
|
680
|
+
### 10.8 External Program Integration (CPI)
|
|
681
|
+
|
|
682
|
+
Core pattern: call external Solana programs from within your contract via interfaces.
|
|
683
|
+
|
|
684
|
+
```v
|
|
685
|
+
// Non-Anchor program (bincode, manual discriminators)
|
|
686
|
+
interface ExternalProgram @program("ExternalProgramID111111111111111111111111111") {
|
|
687
|
+
process @discriminator(1) (
|
|
688
|
+
state: Account,
|
|
689
|
+
authority: Account,
|
|
690
|
+
amount: u64
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// Anchor program (borsh, auto discriminators)
|
|
695
|
+
interface AnchorProgram @anchor @program("AnchorProgramID11111111111111111111111111111") {
|
|
696
|
+
execute( // discriminator auto-generated
|
|
697
|
+
config: Account,
|
|
698
|
+
user: Account,
|
|
699
|
+
value: u64
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
pub perform_action(
|
|
704
|
+
local_state: MyState @mut,
|
|
705
|
+
external_account: account @mut,
|
|
706
|
+
user: account @signer,
|
|
707
|
+
amount: u64
|
|
708
|
+
) {
|
|
709
|
+
require(local_state.authority == user.key);
|
|
710
|
+
require(amount > 0);
|
|
711
|
+
|
|
712
|
+
// CPI to external program
|
|
713
|
+
ExternalProgram.process(external_account, user, amount);
|
|
714
|
+
|
|
715
|
+
// Update local state after CPI
|
|
716
|
+
local_state.last_amount = amount;
|
|
717
|
+
local_state.call_count = local_state.call_count + 1;
|
|
718
|
+
}
|
|
719
|
+
```
|
|
410
720
|
|
|
411
|
-
|
|
412
|
-
- authority-gated custody, withdraw invariants
|
|
413
|
-
2. Escrow:
|
|
414
|
-
- lifecycle transitions, dual-party authorization
|
|
415
|
-
3. Token/mint authority:
|
|
416
|
-
- supply accounting, freeze/delegate controls
|
|
417
|
-
4. AMM/orderbook:
|
|
418
|
-
- conservation math, deterministic settlement
|
|
419
|
-
5. Lending/perps/stablecoin:
|
|
420
|
-
- collateral/liquidation thresholds, risk checks
|
|
721
|
+
**Key ingredients:** interface with `@program`, `@discriminator` (or `@anchor` for auto), `Account` types for CPI params, dot-notation calls, `account @mut` function params for all CPI accounts, mixing CPI calls with local state updates.
|
|
421
722
|
|
|
422
723
|
## 11) Mainnet Safety Policy
|
|
423
724
|
|
|
@@ -464,3 +765,327 @@ Complete means:
|
|
|
464
765
|
3. Avoid hidden defaults for deploy/CPI critical parameters.
|
|
465
766
|
4. Keep changes auditable and reproducible.
|
|
466
767
|
5. If uncertain, inspect compiler/CLI source directly.
|
|
768
|
+
|
|
769
|
+
## 15) Agent One-Shot Contract Generation Procedure
|
|
770
|
+
|
|
771
|
+
Follow this procedure to produce correct 5IVE contracts on first compilation, regardless of contract type.
|
|
772
|
+
|
|
773
|
+
### Step 1: Define account schemas
|
|
774
|
+
- Identify every distinct on-chain state object your contract needs.
|
|
775
|
+
- Each gets an `account Name { ... }` block with **all fields terminated by `;`**.
|
|
776
|
+
- Choose field types from: `pubkey`, `u8`–`u128`, `i8`–`i64`, `bool`, `string<N>`.
|
|
777
|
+
- Include an `authority: pubkey;` field on any account that needs access control.
|
|
778
|
+
- Include a `status: u8;` field for lifecycle/state-machine accounts.
|
|
779
|
+
|
|
780
|
+
### Step 2: Define initializer functions
|
|
781
|
+
- For each account that users create at runtime, write an `init_*` function.
|
|
782
|
+
- The account parameter uses the full attribute stack: `Type @mut @init(payer=name, space=bytes) @signer`.
|
|
783
|
+
- The payer is `account @mut @signer`.
|
|
784
|
+
- Set every field to a known value (don't leave uninitialized fields).
|
|
785
|
+
- Return `-> pubkey` with `return account.key;` when callers need the address.
|
|
786
|
+
|
|
787
|
+
### Step 3: Define interfaces (if calling external programs)
|
|
788
|
+
- Declare `interface Name @program("...") { ... }` at the top of your file.
|
|
789
|
+
- Each method gets `@discriminator(N)` as a single u8 inline: `method @discriminator(N) (...)`.
|
|
790
|
+
- Use `Account` type for on-chain account params, scalar types for data params.
|
|
791
|
+
- **Do not** add `@serializer(...)` — bincode is the default.
|
|
792
|
+
|
|
793
|
+
### Step 4: Define action functions
|
|
794
|
+
- Every state-mutating function takes the relevant account(s) as `AccountType @mut`.
|
|
795
|
+
- Authorization: take an `account @signer` parameter, then `require(state.authority == signer.key);`.
|
|
796
|
+
- Guards: use `require()` with any comparison operator (`==`, `!=`, `<`, `<=`, `>`, `>=`, `!`).
|
|
797
|
+
- For balance operations: always check `require(source.balance >= amount);` before subtraction.
|
|
798
|
+
- For state machines: check `require(state.status == EXPECTED_STATUS);` before transition.
|
|
799
|
+
- Use `let` for intermediate computations (type inference handles it).
|
|
800
|
+
- For CPI calls: pass `account`-typed params directly (not `.key`), use dot notation `Interface.method(...)`.
|
|
801
|
+
|
|
802
|
+
### Step 5: Define read/query functions
|
|
803
|
+
- Use `-> ReturnType` syntax for functions that return values.
|
|
804
|
+
- `return state.field;` to return account data.
|
|
805
|
+
|
|
806
|
+
### Step 6: Compile and verify
|
|
807
|
+
- Run `5ive build` or `5ive compile src/main.v -o build/main.five`.
|
|
808
|
+
- Fix any parser errors (most common: missing `;` in account fields).
|
|
809
|
+
|
|
810
|
+
### Syntax quick-reference
|
|
811
|
+
|
|
812
|
+
| Pattern | Syntax |
|
|
813
|
+
|---|---|
|
|
814
|
+
| Account field | `name: type;` (semicolon required) |
|
|
815
|
+
| Init parameter | `acc: Type @mut @init(payer=p, space=N) @signer` |
|
|
816
|
+
| Signer parameter | `caller: account @signer` or `caller: account @mut @signer` |
|
|
817
|
+
| Ownership check | `require(state.authority == caller.key);` |
|
|
818
|
+
| Balance guard | `require(state.balance >= amount);` |
|
|
819
|
+
| Boolean guard | `require(!state.is_locked);` |
|
|
820
|
+
| Zero-amount guard | `require(amount > 0);` |
|
|
821
|
+
| Revoke authority | `state.authority = 0;` |
|
|
822
|
+
| Status transition | `state.status = 1;` |
|
|
823
|
+
| Local variable | `let x = expr;` |
|
|
824
|
+
| Return value | `pub fn(...) -> u64 { return state.value; }` |
|
|
825
|
+
| Fixed string field | `name: string<32>;` |
|
|
826
|
+
| Zero-init pubkey | `state.delegate = 0;` or `state.delegate = pubkey(0);` |
|
|
827
|
+
| Interface decl | `interface Name @program("...") { ... }` |
|
|
828
|
+
| Interface method | `method @discriminator(N) (param: Account, val: u64);` |
|
|
829
|
+
| CPI call | `InterfaceName.method(acct_param, value);` |
|
|
830
|
+
| CPI account param | `name: account @mut` (not `pubkey`) |
|
|
831
|
+
|
|
832
|
+
## 16) Reference Implementations
|
|
833
|
+
|
|
834
|
+
Three verified, compilable patterns covering distinct contract archetypes. Use as canonical references.
|
|
835
|
+
|
|
836
|
+
### 16.1 Token (Supply Accounting + Delegation + Freeze)
|
|
837
|
+
|
|
838
|
+
```v
|
|
839
|
+
account Mint {
|
|
840
|
+
authority: pubkey;
|
|
841
|
+
freeze_authority: pubkey;
|
|
842
|
+
supply: u64;
|
|
843
|
+
decimals: u8;
|
|
844
|
+
name: string<32>;
|
|
845
|
+
symbol: string<32>;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
account TokenAccount {
|
|
849
|
+
owner: pubkey;
|
|
850
|
+
mint: pubkey;
|
|
851
|
+
balance: u64;
|
|
852
|
+
is_frozen: bool;
|
|
853
|
+
delegate: pubkey;
|
|
854
|
+
delegated_amount: u64;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
pub init_mint(
|
|
858
|
+
mint_account: Mint @mut @init(payer=authority, space=256) @signer,
|
|
859
|
+
authority: account @mut @signer,
|
|
860
|
+
freeze_authority: pubkey,
|
|
861
|
+
decimals: u8,
|
|
862
|
+
name: string<32>,
|
|
863
|
+
symbol: string<32>
|
|
864
|
+
) -> pubkey {
|
|
865
|
+
require(decimals <= 20);
|
|
866
|
+
mint_account.authority = authority.key;
|
|
867
|
+
mint_account.freeze_authority = freeze_authority;
|
|
868
|
+
mint_account.supply = 0;
|
|
869
|
+
mint_account.decimals = decimals;
|
|
870
|
+
mint_account.name = name;
|
|
871
|
+
mint_account.symbol = symbol;
|
|
872
|
+
return mint_account.key;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
pub transfer(
|
|
876
|
+
source: TokenAccount @mut,
|
|
877
|
+
destination: TokenAccount @mut,
|
|
878
|
+
owner: account @signer,
|
|
879
|
+
amount: u64
|
|
880
|
+
) {
|
|
881
|
+
require(source.owner == owner.key);
|
|
882
|
+
require(source.balance >= amount);
|
|
883
|
+
require(source.mint == destination.mint);
|
|
884
|
+
require(!source.is_frozen);
|
|
885
|
+
require(!destination.is_frozen);
|
|
886
|
+
require(amount > 0);
|
|
887
|
+
source.balance = source.balance - amount;
|
|
888
|
+
destination.balance = destination.balance + amount;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
pub approve(
|
|
892
|
+
source: TokenAccount @mut,
|
|
893
|
+
owner: account @signer,
|
|
894
|
+
delegate: pubkey,
|
|
895
|
+
amount: u64
|
|
896
|
+
) {
|
|
897
|
+
require(source.owner == owner.key);
|
|
898
|
+
source.delegate = delegate;
|
|
899
|
+
source.delegated_amount = amount;
|
|
900
|
+
}
|
|
901
|
+
```
|
|
902
|
+
|
|
903
|
+
**Patterns exercised:** `@init` stacking, supply accounting, freeze guards, delegation, `.key` extraction, `string<N>`, `-> pubkey` return.
|
|
904
|
+
|
|
905
|
+
### 16.2 Vault (Custody + Authority Gating)
|
|
906
|
+
|
|
907
|
+
```v
|
|
908
|
+
account Vault {
|
|
909
|
+
authority: pubkey;
|
|
910
|
+
balance: u64;
|
|
911
|
+
is_locked: bool;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
pub init_vault(
|
|
915
|
+
vault: Vault @mut @init(payer=creator, space=128) @signer,
|
|
916
|
+
creator: account @mut @signer
|
|
917
|
+
) -> pubkey {
|
|
918
|
+
vault.authority = creator.key;
|
|
919
|
+
vault.balance = 0;
|
|
920
|
+
vault.is_locked = false;
|
|
921
|
+
return vault.key;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
pub deposit(
|
|
925
|
+
vault: Vault @mut,
|
|
926
|
+
depositor: account @signer,
|
|
927
|
+
amount: u64
|
|
928
|
+
) {
|
|
929
|
+
require(!vault.is_locked);
|
|
930
|
+
require(amount > 0);
|
|
931
|
+
vault.balance = vault.balance + amount;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
pub withdraw(
|
|
935
|
+
vault: Vault @mut,
|
|
936
|
+
authority: account @signer,
|
|
937
|
+
amount: u64
|
|
938
|
+
) {
|
|
939
|
+
require(vault.authority == authority.key);
|
|
940
|
+
require(!vault.is_locked);
|
|
941
|
+
require(vault.balance >= amount);
|
|
942
|
+
require(amount > 0);
|
|
943
|
+
vault.balance = vault.balance - amount;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
pub lock_vault(
|
|
947
|
+
vault: Vault @mut,
|
|
948
|
+
authority: account @signer
|
|
949
|
+
) {
|
|
950
|
+
require(vault.authority == authority.key);
|
|
951
|
+
vault.is_locked = true;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
pub transfer_authority(
|
|
955
|
+
vault: Vault @mut,
|
|
956
|
+
current_authority: account @signer,
|
|
957
|
+
new_authority: pubkey
|
|
958
|
+
) {
|
|
959
|
+
require(vault.authority == current_authority.key);
|
|
960
|
+
vault.authority = new_authority;
|
|
961
|
+
}
|
|
962
|
+
```
|
|
963
|
+
|
|
964
|
+
**Patterns exercised:** authority gating, boolean lock, balance guards, authority transfer, `@init` stacking.
|
|
965
|
+
|
|
966
|
+
### 16.3 Escrow (Lifecycle State Machine + Dual-Party Auth)
|
|
967
|
+
|
|
968
|
+
```v
|
|
969
|
+
account Escrow {
|
|
970
|
+
seller: pubkey;
|
|
971
|
+
buyer: pubkey;
|
|
972
|
+
amount: u64;
|
|
973
|
+
status: u8;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
pub create_escrow(
|
|
977
|
+
escrow: Escrow @mut @init(payer=seller, space=128) @signer,
|
|
978
|
+
seller: account @mut @signer,
|
|
979
|
+
buyer: pubkey,
|
|
980
|
+
amount: u64
|
|
981
|
+
) -> pubkey {
|
|
982
|
+
require(amount > 0);
|
|
983
|
+
escrow.seller = seller.key;
|
|
984
|
+
escrow.buyer = buyer;
|
|
985
|
+
escrow.amount = amount;
|
|
986
|
+
escrow.status = 0;
|
|
987
|
+
return escrow.key;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
pub fund_escrow(
|
|
991
|
+
escrow: Escrow @mut,
|
|
992
|
+
buyer: account @signer,
|
|
993
|
+
amount: u64
|
|
994
|
+
) {
|
|
995
|
+
require(escrow.buyer == buyer.key);
|
|
996
|
+
require(escrow.status == 0);
|
|
997
|
+
require(amount == escrow.amount);
|
|
998
|
+
escrow.status = 1;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
pub release(
|
|
1002
|
+
escrow: Escrow @mut,
|
|
1003
|
+
buyer: account @signer
|
|
1004
|
+
) {
|
|
1005
|
+
require(escrow.buyer == buyer.key);
|
|
1006
|
+
require(escrow.status == 1);
|
|
1007
|
+
escrow.status = 2;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
pub cancel(
|
|
1011
|
+
escrow: Escrow @mut,
|
|
1012
|
+
seller: account @signer
|
|
1013
|
+
) {
|
|
1014
|
+
require(escrow.seller == seller.key);
|
|
1015
|
+
require(escrow.status == 0);
|
|
1016
|
+
escrow.status = 3;
|
|
1017
|
+
}
|
|
1018
|
+
```
|
|
1019
|
+
|
|
1020
|
+
**Patterns exercised:** integer status for state machine, dual-party authorization, lifecycle transitions, exact-amount matching.
|
|
1021
|
+
|
|
1022
|
+
### 16.4 CPI to External Program (Interface + Cross-Program Calls)
|
|
1023
|
+
|
|
1024
|
+
```v
|
|
1025
|
+
// Interface for external program (non-Anchor, bincode)
|
|
1026
|
+
interface ExternalProgram @program("ExternalProgramID111111111111111111111111111") {
|
|
1027
|
+
update_value @discriminator(5) (
|
|
1028
|
+
state: Account,
|
|
1029
|
+
authority: Account,
|
|
1030
|
+
new_value: u64
|
|
1031
|
+
);
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
// Interface for Anchor program (borsh, auto discriminators)
|
|
1035
|
+
interface AnchorProgram @anchor @program("AnchorProgramID11111111111111111111111111111") {
|
|
1036
|
+
process( // discriminator auto-generated from method name
|
|
1037
|
+
config: Account,
|
|
1038
|
+
user: Account,
|
|
1039
|
+
amount: u64
|
|
1040
|
+
);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
account Controller {
|
|
1044
|
+
authority: pubkey;
|
|
1045
|
+
counter: u64;
|
|
1046
|
+
last_value: u64;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
pub init_controller(
|
|
1050
|
+
controller: Controller @mut @init(payer=creator, space=128) @signer,
|
|
1051
|
+
creator: account @mut @signer
|
|
1052
|
+
) -> pubkey {
|
|
1053
|
+
controller.authority = creator.key;
|
|
1054
|
+
controller.counter = 0;
|
|
1055
|
+
controller.last_value = 0;
|
|
1056
|
+
return controller.key;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
pub call_external(
|
|
1060
|
+
controller: Controller @mut,
|
|
1061
|
+
external_state: account @mut,
|
|
1062
|
+
authority: account @signer,
|
|
1063
|
+
value: u64
|
|
1064
|
+
) {
|
|
1065
|
+
require(controller.authority == authority.key);
|
|
1066
|
+
require(value > 0);
|
|
1067
|
+
|
|
1068
|
+
// CPI to external program
|
|
1069
|
+
ExternalProgram.update_value(external_state, authority, value);
|
|
1070
|
+
|
|
1071
|
+
// Update local state
|
|
1072
|
+
controller.counter = controller.counter + 1;
|
|
1073
|
+
controller.last_value = value;
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
pub call_anchor(
|
|
1077
|
+
controller: Controller @mut,
|
|
1078
|
+
anchor_config: account @mut,
|
|
1079
|
+
user: account @signer,
|
|
1080
|
+
amount: u64
|
|
1081
|
+
) {
|
|
1082
|
+
require(controller.authority == user.key);
|
|
1083
|
+
|
|
1084
|
+
// CPI to Anchor program
|
|
1085
|
+
AnchorProgram.process(anchor_config, user, amount);
|
|
1086
|
+
|
|
1087
|
+
controller.counter = controller.counter + 1;
|
|
1088
|
+
}
|
|
1089
|
+
```
|
|
1090
|
+
|
|
1091
|
+
**Patterns exercised:** dual interface types (bincode with manual discriminators, Anchor with auto discriminators), `@program` + `@discriminator` vs `@anchor`, `Account` types in interfaces, dot-notation CPI calls, `account @mut` params for CPI, local state updates after CPI.
|