@5ive-tech/cli 1.0.19 → 1.0.21
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 +1044 -123
|
Binary file
|
package/package.json
CHANGED
package/templates/AGENTS.md
CHANGED
|
@@ -1,176 +1,1097 @@
|
|
|
1
|
-
# AGENTS.md
|
|
1
|
+
# AGENTS.md - Complete 5IVE DSL Agent Bootstrap
|
|
2
2
|
|
|
3
|
-
This
|
|
3
|
+
This file is written for agents with zero prior 5IVE knowledge.
|
|
4
|
+
It is intentionally self-contained and should be treated as the baseline technical specification for authoring, testing, deploying, and integrating 5IVE programs.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
## 1) What 5IVE Is
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
5IVE is a DSL and toolchain for compiling compact bytecode programs for Solana execution paths.
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
Core surfaces:
|
|
11
|
+
1. Source language: `.v`
|
|
12
|
+
2. Canonical artifact: `.five` (bytecode + ABI)
|
|
13
|
+
3. CLI: `@5ive-tech/cli` (`5ive` canonical command; `five` alias supported)
|
|
14
|
+
4. SDK: `@5ive-tech/sdk` (`FiveSDK`, `FiveProgram`)
|
|
10
15
|
|
|
11
|
-
|
|
12
|
-
- Scope: Applies to any automated edit, patch, or PR created by scripts, CI, LLMs, or other programmatic processes across this repository (all subprojects under `five-org/`).
|
|
16
|
+
## 2) Source of Truth Policy
|
|
13
17
|
|
|
14
|
-
|
|
18
|
+
When docs conflict, resolve in this order:
|
|
19
|
+
1. Compiler/CLI/SDK source code
|
|
20
|
+
2. Package manifests + command definitions
|
|
21
|
+
3. READMEs/examples/docs
|
|
15
22
|
|
|
16
|
-
|
|
23
|
+
Never rely on stale docs when behavior is high-stakes (deploy/execute/CPI encoding).
|
|
17
24
|
|
|
18
|
-
|
|
19
|
-
- Never change code in a way that makes tests pass by hiding bugs or changing tests to match incorrect behavior.
|
|
20
|
-
- When tests fail, investigate and fix the root cause rather than masking failures.
|
|
25
|
+
## 3) Non-Negotiable Workflow
|
|
21
26
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
1. Inspect `five.toml` before code changes.
|
|
28
|
+
2. Compile to `.five`.
|
|
29
|
+
3. Run local/runtime tests.
|
|
30
|
+
4. Deploy with explicit target + program ID resolution path.
|
|
31
|
+
5. Execute and verify confirmed tx metadata (`meta.err == null`).
|
|
32
|
+
6. Record signatures + compute units.
|
|
28
33
|
|
|
29
|
-
3.
|
|
30
|
-
- If a variable is currently unused:
|
|
31
|
-
- Either remove it if truly unnecessary, or
|
|
32
|
-
- Use it meaningfully, or
|
|
33
|
-
- Replace the identifier with an explicit `_name` placeholder only when it is intentionally unused by design (e.g., implementing a trait method that does not use a parameter).
|
|
34
|
-
- Automated agents MUST NOT simply silence unused-variable warnings by prefixing with `_` unless the variable is intentionally unused and this is documented in code comments.
|
|
34
|
+
### 3.1 Strict Authoring Rules
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
- Tests (unit, integration, and golden bytecode tests) reflect the intended behavior and must pass.
|
|
38
|
-
- Agents must add or update tests when behavior changes, not remove or weaken assertions to make a failing build pass.
|
|
36
|
+
These rules are non-negotiable and prevent the most common compilation failures:
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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()`** — `==`, `!=`, `<`, `<=`, `>`, `>=`, `!`.
|
|
45
|
+
8. **Local variables are immutable by default** — use `let x = value;` for immutable bindings. **Use `let mut x = value;` if the variable will be reassigned** (e.g., in conditional branches). Attempting to reassign an immutable local causes a compiler error.
|
|
43
46
|
|
|
44
|
-
|
|
47
|
+
## 4) DSL Feature Inventory (Deep)
|
|
45
48
|
|
|
46
|
-
|
|
49
|
+
This section enumerates language features discovered from parser/compiler code and repo examples.
|
|
47
50
|
|
|
48
|
-
|
|
51
|
+
### 4.1 Top-level declarations
|
|
52
|
+
Observed and/or parsed:
|
|
53
|
+
1. `account Name { ... }` — **all fields must be terminated with `;`**
|
|
54
|
+
2. Global fields/variables (including `mut`)
|
|
55
|
+
3. `init { ... }` block
|
|
56
|
+
4. `constraints { ... }` block
|
|
57
|
+
5. Function/instruction definitions (`pub`, `fn`, optional `instruction` keyword)
|
|
58
|
+
6. `event Name { ... }` definitions
|
|
59
|
+
7. `interface Name ... { ... }` definitions
|
|
60
|
+
8. `use` / `import` statements
|
|
61
|
+
9. Legacy `script Name { ... }` wrapper (parser-supported)
|
|
49
62
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
63
|
+
```v
|
|
64
|
+
// ✅ CORRECT — semicolons required
|
|
65
|
+
account Mint {
|
|
66
|
+
authority: pubkey;
|
|
67
|
+
supply: u64;
|
|
68
|
+
decimals: u8;
|
|
69
|
+
}
|
|
53
70
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
71
|
+
// ❌ WRONG — parser failure
|
|
72
|
+
account Mint {
|
|
73
|
+
authority: pubkey
|
|
74
|
+
supply: u64
|
|
75
|
+
}
|
|
76
|
+
```
|
|
57
77
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
78
|
+
### 4.2 Function definition forms
|
|
79
|
+
Parser accepts flexible forms:
|
|
80
|
+
1. `pub add(...) -> ... { ... }`
|
|
81
|
+
2. `fn add(...) { ... }`
|
|
82
|
+
3. `instruction add(...) { ... }`
|
|
83
|
+
4. `pub fn add(...) { ... }`
|
|
61
84
|
|
|
62
|
-
4.
|
|
63
|
-
|
|
64
|
-
|
|
85
|
+
### 4.3 Parameter system
|
|
86
|
+
Each parameter supports:
|
|
87
|
+
1. Name + type: `x: u64`
|
|
88
|
+
2. Optional marker: `x?: u64`
|
|
89
|
+
3. Default value: `x: u64 = 10`
|
|
90
|
+
4. Attributes before or after type
|
|
65
91
|
|
|
66
|
-
|
|
67
|
-
|
|
92
|
+
Common attributes:
|
|
93
|
+
1. `@signer`
|
|
94
|
+
2. `@mut`
|
|
95
|
+
3. `@init`
|
|
96
|
+
4. Generic form: `@attribute(args...)`
|
|
97
|
+
5. Template-observed relation constraints: `@has(field)`
|
|
68
98
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
99
|
+
### 4.4 `@init` config support
|
|
100
|
+
`@init` can include config arguments:
|
|
101
|
+
1. `payer=...`
|
|
102
|
+
2. `space=...`
|
|
103
|
+
3. `seeds=[...]`
|
|
104
|
+
4. `bump=...`
|
|
75
105
|
|
|
76
|
-
|
|
106
|
+
Examples also show legacy bracket seed forms after `@init`.
|
|
77
107
|
|
|
78
|
-
|
|
108
|
+
**Attribute stacking order for account parameters (empirically verified):**
|
|
79
109
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
```rust
|
|
84
|
-
// SAFETY: The pointer `p` is guaranteed to be valid and aligned for `T` because
|
|
85
|
-
// it comes from a pinned heap allocation that is not mutated concurrently.
|
|
86
|
-
unsafe { &*p }
|
|
87
|
-
```
|
|
88
|
-
- Agents must not remove such comments or replace `unsafe` usage with unchecked "safe" code without a thorough manual review and performance of safety tests.
|
|
110
|
+
```
|
|
111
|
+
Type @mut @init(payer=name, space=bytes) @signer
|
|
112
|
+
```
|
|
89
113
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
114
|
+
Example:
|
|
115
|
+
```v
|
|
116
|
+
pub init_mint(
|
|
117
|
+
mint_account: Mint @mut @init(payer=authority, space=256) @signer,
|
|
118
|
+
authority: account @mut @signer,
|
|
119
|
+
...
|
|
120
|
+
)
|
|
121
|
+
```
|
|
95
122
|
|
|
96
|
-
|
|
97
|
-
- Avoid leaving `println!` or ad-hoc `eprintln!` in production code.
|
|
98
|
-
- If temporary debugging is required for a patch, include a clear TODO comment and remove it before final merge.
|
|
123
|
+
Order: (1) Type declaration → (2) `@mut` → (3) `@init(...)` → (4) `@signer`.
|
|
99
124
|
|
|
100
|
-
|
|
125
|
+
### 4.5 Types
|
|
126
|
+
Supported/parsed type families:
|
|
127
|
+
1. Primitive numeric/bool/pubkey/string types (`u8..u128`, `i8..i64`, `bool`, `pubkey`, `string`)
|
|
128
|
+
2. `Account` type and account-typed params
|
|
129
|
+
3. **Sized strings: `string<N>`** — production-safe, use in accounts and function parameters
|
|
130
|
+
4. Arrays:
|
|
131
|
+
- Rust style: `[T; N]`
|
|
132
|
+
- TypeScript-style sized: `T[N]`
|
|
133
|
+
- TypeScript-style dynamic: `T[]`
|
|
134
|
+
5. Tuples: `(T1, T2, ...)`
|
|
135
|
+
6. Inline struct types: `{ field: Type, ... }`
|
|
136
|
+
7. Generic types:
|
|
137
|
+
- `Option<T>`
|
|
138
|
+
- `Result<T, E>`
|
|
139
|
+
- nested generics (`Option<Option<u64>>` etc.)
|
|
140
|
+
8. Namespaced/custom types: `module::Type`
|
|
141
|
+
9. Optional account fields in structs/accounts: `field?: Type`
|
|
142
|
+
10. **`pubkey(0)` and integer `0`** — valid for zero-initialization of pubkey fields (interchangeable)
|
|
101
143
|
|
|
102
|
-
|
|
144
|
+
### 4.6 Statements
|
|
145
|
+
Observed and parser-supported:
|
|
146
|
+
1. `let` declarations (with `mut` and optional type annotation)
|
|
147
|
+
- Type inference works: `let is_owner = source.owner == authority.key;` infers `bool`
|
|
148
|
+
- Use `let` without explicit annotation for boolean and scalar expressions
|
|
149
|
+
- **Immutability by default**: `let x = value;` creates an immutable binding; reassignment will fail
|
|
150
|
+
- **For reassignable variables, use `let mut`**: `let mut x: u64 = 0; ... x = new_value;`
|
|
151
|
+
- Example: `let mut shares: u64 = 0; if (condition) { shares = computed_value; }`
|
|
152
|
+
2. Assignment:
|
|
153
|
+
- direct: `x = y`
|
|
154
|
+
- compound: `+=`, `-=`, `*=`, `/=`, `<<=`, `>>=`, `&=`, `|=`, `^=`
|
|
155
|
+
3. Field assignment: `obj.field = value`
|
|
156
|
+
4. Return statements (`return`, `return value`) — see §4.13 for return type syntax
|
|
157
|
+
5. Guard/assertion: `require(condition)` — **all operators verified:**
|
|
158
|
+
- Comparison: `==`, `!=`, `<`, `<=`, `>`, `>=`
|
|
159
|
+
- Boolean negation: `!expr`
|
|
160
|
+
- Logical: `&&`, `||`
|
|
161
|
+
- Example: `require(source.balance >= amount);`
|
|
162
|
+
- Example: `require(!account.is_frozen);`
|
|
163
|
+
6. Conditionals:
|
|
164
|
+
- `if (...) {}`
|
|
165
|
+
- `else if (...) {}`
|
|
166
|
+
- `else {}`
|
|
167
|
+
- Conditionals support nested `require()` statements and multiple assignments in both branches
|
|
168
|
+
7. Pattern matching: `match expr { ... }`, with optional arm guards (`if ...`)
|
|
169
|
+
8. Loops:
|
|
170
|
+
- `while (...) { ... }`
|
|
171
|
+
- `for (init; cond; update) { ... }`
|
|
172
|
+
- `for (item in iterable) { ... }`
|
|
173
|
+
- `do { ... } while (...);`
|
|
174
|
+
9. Tuple destructuring:
|
|
175
|
+
- declaration style: `let (a, b) = expr`
|
|
176
|
+
- assignment style for tuple targets
|
|
177
|
+
10. Event emission: `emit EventName { field: value, ... }`
|
|
178
|
+
11. Expression statements (function/method calls, constructors like `Some(...)`)
|
|
103
179
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
180
|
+
### 4.7 Expressions and operators
|
|
181
|
+
Parser handles:
|
|
182
|
+
1. Arithmetic: `+`, `-`, `*`, `/`, `%`
|
|
183
|
+
2. Checked-arithmetic tokens in grammar: `+?`, `-?`, `*?`
|
|
184
|
+
- Some repo tests indicate these were replaced/legacy in current examples.
|
|
185
|
+
3. Comparison: `==`, `!=`, `<`, `<=`, `>`, `>=`
|
|
186
|
+
4. Boolean: `&&`, `||`, `!`
|
|
187
|
+
5. Bitwise: `&`, `|`, `^`, `~`
|
|
188
|
+
6. Shifts/bit ops: `<<`, `>>`, `>>>`, `<<<`
|
|
189
|
+
7. Range operator: `..`
|
|
190
|
+
8. Unary `+`/`-`
|
|
191
|
+
9. Cast syntax: `expr as Type`
|
|
192
|
+
10. Error propagation postfix: `expr?`
|
|
193
|
+
11. Field access: `obj.field`
|
|
194
|
+
12. Tuple access: `obj.0`
|
|
195
|
+
13. Array indexing: `arr[idx]`
|
|
196
|
+
14. Function calls
|
|
197
|
+
15. Method calls: `obj.method(args...)`
|
|
198
|
+
16. Namespaced calls: `module::function(...)`
|
|
199
|
+
17. Struct literals: `{ field: expr, ... }`
|
|
200
|
+
18. Array literals: `[a, b, c]`
|
|
201
|
+
19. Tuple literals: `(a, b)`
|
|
202
|
+
20. Option/Result constructors and patterns:
|
|
203
|
+
- `Some(...)`, `None`
|
|
204
|
+
- `Ok(...)`, `Err(...)`
|
|
108
205
|
|
|
109
|
-
|
|
206
|
+
### 4.8 Imports and modules
|
|
207
|
+
`use`/`import` system supports:
|
|
208
|
+
1. External module specifier via quoted literal
|
|
209
|
+
2. Local module specifier via identifier path
|
|
210
|
+
3. Nested local module paths using `::`
|
|
211
|
+
4. Scoped namespace forms with symbols: `!`, `@`, `#`, `$`, `%`
|
|
212
|
+
5. Member imports:
|
|
213
|
+
- single: `::name`
|
|
214
|
+
- list: `::{a, b}`
|
|
215
|
+
- typed list entries: `method foo`, `interface Bar`
|
|
110
216
|
|
|
111
|
-
|
|
217
|
+
### 4.9 Interfaces and CPI (Cross-Program Invocation)
|
|
112
218
|
|
|
113
|
-
|
|
219
|
+
Interfaces define external program calls. **Empirically verified rules:**
|
|
114
220
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
221
|
+
1. **Program binding:** always use `@program("...")` (the `@` prefix is required)
|
|
222
|
+
2. **Serializer options:**
|
|
223
|
+
- **Default (bincode):** omit `@serializer(...)` — bincode is the default, works for SPL programs and most Solana programs
|
|
224
|
+
- **Anchor programs (borsh):** use `@anchor` marker — automatically sets borsh serializer **and** auto-generates discriminators from method names
|
|
225
|
+
- **Explicit borsh:** use `@serializer("borsh")` if needed without `@anchor`
|
|
226
|
+
3. **Discriminators:**
|
|
227
|
+
- **Manual:** use single `u8` value inline on method: `method @discriminator(N) (...)`
|
|
228
|
+
- **Anchor auto-generation:** `@anchor` interface automatically computes discriminators from method names — **do not** manually specify `@discriminator` with `@anchor`
|
|
229
|
+
- **Format:** single u8 value, **not** array format `@discriminator([3, 0, 0, 0])`
|
|
230
|
+
4. **Account parameters in interfaces:** use `Account` type, **not** `pubkey`
|
|
231
|
+
- `pubkey` is for data values only; `Account` represents an on-chain account passed to the CPI
|
|
232
|
+
5. **Calling interface methods:** use dot notation `InterfaceName.method(...)`, **not** `InterfaceName::method(...)`
|
|
233
|
+
6. **Passing accounts to CPI:** pass `account`-typed parameters directly, **not** `param.key`
|
|
234
|
+
7. **Function parameters for CPI accounts:** must be typed `account @mut` (not `pubkey`)
|
|
121
235
|
|
|
122
|
-
|
|
236
|
+
```v
|
|
237
|
+
// ✅ CORRECT: SPL Token (bincode, manual discriminators)
|
|
238
|
+
interface SPLToken @program("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA") {
|
|
239
|
+
transfer @discriminator(3) (
|
|
240
|
+
source: Account,
|
|
241
|
+
destination: Account,
|
|
242
|
+
authority: Account,
|
|
243
|
+
amount: u64
|
|
244
|
+
);
|
|
245
|
+
}
|
|
123
246
|
|
|
124
|
-
|
|
247
|
+
// ✅ CORRECT: Anchor program (borsh, auto discriminators)
|
|
248
|
+
interface MyAnchorProgram @anchor @program("...") {
|
|
249
|
+
initialize( // discriminator auto-generated from "initialize"
|
|
250
|
+
state: Account,
|
|
251
|
+
authority: Account,
|
|
252
|
+
value: u64
|
|
253
|
+
);
|
|
254
|
+
}
|
|
125
255
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
256
|
+
// ✅ CORRECT: CPI call
|
|
257
|
+
pub call_external(
|
|
258
|
+
external_account: account @mut,
|
|
259
|
+
authority: account @signer,
|
|
260
|
+
value: u64
|
|
261
|
+
) {
|
|
262
|
+
MyAnchorProgram.initialize(external_account, authority, value);
|
|
263
|
+
}
|
|
134
264
|
|
|
135
|
-
|
|
265
|
+
// ❌ WRONG — common mistakes
|
|
266
|
+
// interface Program program("...") ← missing @ on program
|
|
267
|
+
// @discriminator([3, 0, 0, 0]) ← array format, not u8
|
|
268
|
+
// transfer(src: pubkey, dst: pubkey, ...) ← pubkey instead of Account
|
|
269
|
+
// Program::method(...) ← :: instead of .
|
|
270
|
+
// Program.method(account.key, ...) ← .key unnecessary for accounts
|
|
271
|
+
// @anchor with @discriminator(3) ← @anchor auto-generates, don't specify manually
|
|
272
|
+
```
|
|
136
273
|
|
|
137
|
-
|
|
274
|
+
CPI hard rules for agents:
|
|
275
|
+
1. Always use `@program("...")` with correct program ID
|
|
276
|
+
2. For Anchor programs: use `@anchor`, omit `@discriminator`
|
|
277
|
+
3. For non-Anchor programs: set `@discriminator(N)` as single u8 on each method, omit `@serializer`
|
|
278
|
+
4. Use `Account` for on-chain account params, scalar types for data params
|
|
279
|
+
5. Call with dot notation and pass account params directly
|
|
138
280
|
|
|
139
|
-
|
|
140
|
-
|
|
281
|
+
### 4.10 Events and error/enums
|
|
282
|
+
Parser/AST include:
|
|
283
|
+
1. Event definitions + `emit` statements
|
|
284
|
+
2. Enum/error-style definitions (`enum` path in parser)
|
|
141
285
|
|
|
142
|
-
|
|
286
|
+
Production note:
|
|
287
|
+
Treat event/error enum features as available in syntax, but verify runtime/compiler behavior in your exact toolchain version before relying on them for critical flows.
|
|
143
288
|
|
|
144
|
-
|
|
289
|
+
### 4.11 Testing-oriented language features
|
|
290
|
+
From tokenizer/parser support:
|
|
291
|
+
1. `#[...]` test attributes
|
|
292
|
+
2. `test` function parse path
|
|
293
|
+
3. Test attribute names/tokens include:
|
|
294
|
+
- `ignore`
|
|
295
|
+
- `should_fail`
|
|
296
|
+
- `timeout`
|
|
297
|
+
4. Assertion tokens:
|
|
298
|
+
- `assert_eq`
|
|
299
|
+
- `assert_true`
|
|
300
|
+
- `assert_false`
|
|
301
|
+
- `assert_fails`
|
|
302
|
+
- `assert_approx_eq`
|
|
145
303
|
|
|
146
|
-
|
|
304
|
+
Repository tests also use comment-based param conventions (`// @test-params ...`) in many scripts.
|
|
147
305
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
306
|
+
### 4.12 Blockchain-oriented built-ins
|
|
307
|
+
Core built-ins available in all contracts:
|
|
308
|
+
1. `derive_pda(...)` (including bump-return and bump-specified variants)
|
|
309
|
+
2. `get_clock()`
|
|
310
|
+
3. `get_key(...)`
|
|
311
|
+
4. **Account key access: `param.key`** — **core pattern for all `account`-typed parameters.** Use `.key` to extract pubkeys for comparisons and assignments.
|
|
153
312
|
|
|
154
|
-
|
|
313
|
+
```v
|
|
314
|
+
pub action(
|
|
315
|
+
state: MyAccount @mut,
|
|
316
|
+
caller: account @signer,
|
|
317
|
+
...
|
|
318
|
+
) {
|
|
319
|
+
require(state.authority == caller.key); // ownership check
|
|
320
|
+
state.last_actor = caller.key; // record who acted
|
|
321
|
+
}
|
|
322
|
+
```
|
|
155
323
|
|
|
156
|
-
|
|
324
|
+
5. **Authority revocation pattern:** assign `0` to any pubkey field to permanently disable it.
|
|
325
|
+
```v
|
|
326
|
+
state.authority = 0; // revokes authority — irreversible
|
|
327
|
+
```
|
|
157
328
|
|
|
158
|
-
|
|
159
|
-
|
|
329
|
+
### 4.13 Return types and values
|
|
330
|
+
Functions can declare return types with `->` syntax:
|
|
160
331
|
|
|
161
|
-
|
|
162
|
-
|
|
332
|
+
```v
|
|
333
|
+
pub get_value(state: MyAccount) -> u64 {
|
|
334
|
+
return state.amount;
|
|
335
|
+
}
|
|
163
336
|
|
|
164
|
-
|
|
165
|
-
|
|
337
|
+
pub initialize(
|
|
338
|
+
state: MyAccount @mut @init(payer=creator, space=256) @signer,
|
|
339
|
+
creator: account @mut @signer,
|
|
340
|
+
...
|
|
341
|
+
) -> pubkey {
|
|
342
|
+
state.authority = creator.key;
|
|
343
|
+
return state.key;
|
|
344
|
+
}
|
|
345
|
+
```
|
|
166
346
|
|
|
167
|
-
|
|
347
|
+
Confirmed return types: `u8`, `u16`, `u32`, `u64`, `u128`, `i8`..`i64`, `bool`, `pubkey`.
|
|
168
348
|
|
|
169
|
-
##
|
|
349
|
+
## 5) Feature Maturity Matrix (Agent Safety)
|
|
170
350
|
|
|
171
|
-
|
|
172
|
-
|
|
351
|
+
### 5.1 Generally production-oriented (widely used in templates)
|
|
352
|
+
1. Accounts, `@mut`, `@signer`, `@init` (with attribute stacking)
|
|
353
|
+
2. `require` with all comparison operators (`==`, `!=`, `<`, `<=`, `>`, `>=`, `!`)
|
|
354
|
+
3. Basic control flow (`if`, `else`, `while`) with nested logic
|
|
355
|
+
4. Arithmetic/comparison/boolean expressions
|
|
356
|
+
5. `.five` compile/deploy/execute path
|
|
357
|
+
6. `interface` + explicit discriminator + explicit serializer CPI patterns
|
|
358
|
+
7. Return type declarations (`-> Type`) with `return value;`
|
|
359
|
+
8. `string<N>` fixed-size strings in accounts and parameters
|
|
360
|
+
9. `account.key` extraction from `account`-typed parameters
|
|
361
|
+
10. Authority disabling via `0` assignment to pubkey fields
|
|
362
|
+
11. `let` with type inference for scalar/boolean expressions
|
|
363
|
+
12. `pubkey(0)` zero-initialization
|
|
173
364
|
|
|
174
|
-
|
|
365
|
+
### 5.2 Available but validate per-version before critical use
|
|
366
|
+
1. Match expressions with `Option`/`Result`
|
|
367
|
+
2. Tuple destructuring and tuple returns
|
|
368
|
+
3. Advanced loop forms (`for`, `do-while`)
|
|
369
|
+
4. Event definition/emit workflows
|
|
370
|
+
5. Namespaced imports and scoped namespace symbols
|
|
371
|
+
6. Bitwise/shift operator-heavy code
|
|
175
372
|
|
|
176
|
-
|
|
373
|
+
### 5.3 Parser tokens exist; treat as reserved/experimental unless proven in your path
|
|
374
|
+
1. `query`, `when`, `realloc`, `pda` keywords
|
|
375
|
+
2. Some assertion/test keyword paths in non-test production code
|
|
376
|
+
3. Legacy checked-arithmetic operators (`+?`, `-?`, `*?`) where examples indicate migration
|
|
377
|
+
|
|
378
|
+
## 6) CLI Canonical Usage
|
|
379
|
+
|
|
380
|
+
### 6.1 Install and identity
|
|
381
|
+
```bash
|
|
382
|
+
npm install -g @5ive-tech/cli
|
|
383
|
+
5ive --version
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### 6.2 Initialize
|
|
387
|
+
```bash
|
|
388
|
+
5ive init my-program
|
|
389
|
+
cd my-program
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### 6.3 Compile
|
|
393
|
+
```bash
|
|
394
|
+
5ive compile src/main.v -o build/main.five
|
|
395
|
+
# or project-aware
|
|
396
|
+
5ive build
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### 6.4 Local execute
|
|
400
|
+
```bash
|
|
401
|
+
5ive execute build/main.five --local -f 0
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### 6.5 Configure devnet
|
|
405
|
+
```bash
|
|
406
|
+
5ive config init
|
|
407
|
+
5ive config set --target devnet
|
|
408
|
+
5ive config set --keypair ~/.config/solana/id.json
|
|
409
|
+
5ive config set --program-id <FIVE_VM_PROGRAM_ID> --target devnet
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### 6.6 Deploy + execute on-chain
|
|
413
|
+
```bash
|
|
414
|
+
5ive deploy build/main.five --target devnet
|
|
415
|
+
5ive execute build/main.five --target devnet -f 0
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### 6.7 Advanced deploy modes
|
|
419
|
+
```bash
|
|
420
|
+
5ive deploy build/main.five --target devnet --optimized --progress
|
|
421
|
+
5ive deploy build/main.five --target devnet --force-chunked --chunk-size 900
|
|
422
|
+
5ive deploy build/main.five --target devnet --dry-run --format json
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### 6.8 Test modes
|
|
426
|
+
```bash
|
|
427
|
+
5ive test --sdk-runner
|
|
428
|
+
5ive test tests/ --on-chain --target devnet
|
|
429
|
+
5ive test --sdk-runner --format json
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
## 7) Program ID and Target Resolution
|
|
433
|
+
|
|
434
|
+
On-chain command precedence (`deploy`, `execute`, `namespace`):
|
|
435
|
+
1. `--program-id`
|
|
436
|
+
2. `five.toml [deploy].program_id`
|
|
437
|
+
3. `5ive config` value for current target
|
|
438
|
+
4. `FIVE_PROGRAM_ID`
|
|
439
|
+
|
|
440
|
+
Never run on-chain commands with ambiguous target/program-id context.
|
|
441
|
+
|
|
442
|
+
## 8) SDK Canonical Usage
|
|
443
|
+
|
|
444
|
+
### 8.1 Load artifact
|
|
445
|
+
```ts
|
|
446
|
+
import fs from "fs";
|
|
447
|
+
import { FiveSDK } from "@5ive-tech/sdk";
|
|
448
|
+
|
|
449
|
+
const fiveFileText = fs.readFileSync("build/main.five", "utf8");
|
|
450
|
+
const { abi } = await FiveSDK.loadFiveFile(fiveFileText);
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
### 8.2 Program client
|
|
454
|
+
```ts
|
|
455
|
+
import { FiveProgram } from "@5ive-tech/sdk";
|
|
456
|
+
|
|
457
|
+
const program = FiveProgram.fromABI("<SCRIPT_ACCOUNT>", abi, {
|
|
458
|
+
fiveVMProgramId: "<FIVE_VM_PROGRAM_ID>",
|
|
459
|
+
vmStateAccount: "<VM_STATE_ACCOUNT>",
|
|
460
|
+
feeReceiverAccount: "<FEE_RECEIVER_ACCOUNT>",
|
|
461
|
+
});
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### 8.3 Execution verification pattern
|
|
465
|
+
1. Build instruction via `program.function(...).accounts(...).args(...).instruction()`
|
|
466
|
+
2. Submit with preflight enabled
|
|
467
|
+
3. Fetch confirmed tx
|
|
468
|
+
4. Assert `meta.err == null`
|
|
469
|
+
5. Record `meta.computeUnitsConsumed`
|
|
470
|
+
|
|
471
|
+
### 8.4 SDK program ID resolution precedence
|
|
472
|
+
1. Explicit `fiveVMProgramId`
|
|
473
|
+
2. `FiveSDK.setDefaultProgramId(...)`
|
|
474
|
+
3. `FIVE_PROGRAM_ID`
|
|
475
|
+
4. release-baked default (if present)
|
|
476
|
+
|
|
477
|
+
## 9) Frontend Integration Baseline
|
|
478
|
+
|
|
479
|
+
1. Build execute instructions via SDK (`FiveProgram`), not custom serializers.
|
|
480
|
+
2. Keep network selection explicit (`localnet`, `devnet`, `mainnet`).
|
|
481
|
+
3. Surface signatures, CU metrics, and rich error states.
|
|
482
|
+
4. Use LSP-backed editing where available to reduce DSL mistakes.
|
|
483
|
+
|
|
484
|
+
## 10) Contract Pattern Recipes
|
|
485
|
+
|
|
486
|
+
This section provides composable patterns for the most common contract archetypes. When building a novel contract, identify which patterns apply and combine them.
|
|
487
|
+
|
|
488
|
+
### 10.1 Authority-Gated State (Vault, Treasury, Config)
|
|
489
|
+
|
|
490
|
+
Core pattern: one or more pubkey fields control who can mutate state. Used in almost every contract.
|
|
491
|
+
|
|
492
|
+
```v
|
|
493
|
+
account Config {
|
|
494
|
+
authority: pubkey;
|
|
495
|
+
value: u64;
|
|
496
|
+
is_locked: bool;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
pub update_value(
|
|
500
|
+
config: Config @mut,
|
|
501
|
+
authority: account @signer,
|
|
502
|
+
new_value: u64
|
|
503
|
+
) {
|
|
504
|
+
require(config.authority == authority.key);
|
|
505
|
+
require(!config.is_locked);
|
|
506
|
+
config.value = new_value;
|
|
507
|
+
}
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
**Key ingredients:** ownership check via `.key`, boolean guard with `!`, field mutation.
|
|
511
|
+
|
|
512
|
+
### 10.2 Custody & Withdraw (Vault, Staking)
|
|
513
|
+
|
|
514
|
+
Core pattern: deposit into an account, enforce balance invariants on withdraw.
|
|
515
|
+
|
|
516
|
+
```v
|
|
517
|
+
pub deposit(
|
|
518
|
+
vault: VaultAccount @mut,
|
|
519
|
+
depositor: account @signer,
|
|
520
|
+
amount: u64
|
|
521
|
+
) {
|
|
522
|
+
require(amount > 0);
|
|
523
|
+
vault.balance = vault.balance + amount;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
pub withdraw(
|
|
527
|
+
vault: VaultAccount @mut,
|
|
528
|
+
authority: account @signer,
|
|
529
|
+
amount: u64
|
|
530
|
+
) {
|
|
531
|
+
require(vault.authority == authority.key);
|
|
532
|
+
require(vault.balance >= amount);
|
|
533
|
+
require(amount > 0);
|
|
534
|
+
vault.balance = vault.balance - amount;
|
|
535
|
+
}
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
**Key ingredients:** `>= amount` balance guard, arithmetic on fields, `> 0` zero-amount prevention.
|
|
539
|
+
|
|
540
|
+
### 10.3 Lifecycle State Machine (Escrow, Auction, Proposal)
|
|
541
|
+
|
|
542
|
+
Core pattern: a status field controls which operations are valid. Transitions are guarded.
|
|
543
|
+
|
|
544
|
+
```v
|
|
545
|
+
account Escrow {
|
|
546
|
+
seller: pubkey;
|
|
547
|
+
buyer: pubkey;
|
|
548
|
+
amount: u64;
|
|
549
|
+
status: u8; // 0=open, 1=funded, 2=released, 3=cancelled
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
pub fund_escrow(
|
|
553
|
+
escrow: Escrow @mut,
|
|
554
|
+
buyer: account @signer,
|
|
555
|
+
amount: u64
|
|
556
|
+
) {
|
|
557
|
+
require(escrow.buyer == buyer.key);
|
|
558
|
+
require(escrow.status == 0);
|
|
559
|
+
require(amount == escrow.amount);
|
|
560
|
+
escrow.status = 1;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
pub release_escrow(
|
|
564
|
+
escrow: Escrow @mut,
|
|
565
|
+
buyer: account @signer
|
|
566
|
+
) {
|
|
567
|
+
require(escrow.buyer == buyer.key);
|
|
568
|
+
require(escrow.status == 1);
|
|
569
|
+
escrow.status = 2;
|
|
570
|
+
}
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
**Key ingredients:** integer status field with `==` checks for state transitions, dual-party authorization.
|
|
574
|
+
|
|
575
|
+
### 10.4 Supply Accounting (Token, Mint, Points)
|
|
576
|
+
|
|
577
|
+
Core pattern: a central supply counter stays synchronized with distributed balances.
|
|
578
|
+
|
|
579
|
+
```v
|
|
580
|
+
pub mint_to(
|
|
581
|
+
supply_state: SupplyAccount @mut,
|
|
582
|
+
destination: BalanceAccount @mut,
|
|
583
|
+
authority: account @signer,
|
|
584
|
+
amount: u64
|
|
585
|
+
) {
|
|
586
|
+
require(supply_state.authority == authority.key);
|
|
587
|
+
require(amount > 0);
|
|
588
|
+
supply_state.total_supply = supply_state.total_supply + amount;
|
|
589
|
+
destination.balance = destination.balance + amount;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
pub burn(
|
|
593
|
+
supply_state: SupplyAccount @mut,
|
|
594
|
+
source: BalanceAccount @mut,
|
|
595
|
+
owner: account @signer,
|
|
596
|
+
amount: u64
|
|
597
|
+
) {
|
|
598
|
+
require(source.owner == owner.key);
|
|
599
|
+
require(source.balance >= amount);
|
|
600
|
+
source.balance = source.balance - amount;
|
|
601
|
+
supply_state.total_supply = supply_state.total_supply - amount;
|
|
602
|
+
}
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
**Key ingredients:** paired increment/decrement across two accounts, conservation invariant.
|
|
606
|
+
|
|
607
|
+
### 10.5 Delegation & Approval (Token, DAO, Proxy)
|
|
608
|
+
|
|
609
|
+
Core pattern: an owner grants limited permissions to a delegate.
|
|
610
|
+
|
|
611
|
+
```v
|
|
612
|
+
pub approve(
|
|
613
|
+
state: DelegableAccount @mut,
|
|
614
|
+
owner: account @signer,
|
|
615
|
+
delegate: pubkey,
|
|
616
|
+
limit: u64
|
|
617
|
+
) {
|
|
618
|
+
require(state.owner == owner.key);
|
|
619
|
+
state.delegate = delegate;
|
|
620
|
+
state.delegated_limit = limit;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
pub revoke(
|
|
624
|
+
state: DelegableAccount @mut,
|
|
625
|
+
owner: account @signer
|
|
626
|
+
) {
|
|
627
|
+
require(state.owner == owner.key);
|
|
628
|
+
state.delegate = 0;
|
|
629
|
+
state.delegated_limit = 0;
|
|
630
|
+
}
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
**Key ingredients:** delegate pubkey field, limit tracking, zero-assignment to revoke.
|
|
634
|
+
|
|
635
|
+
### 10.6 Conservation Math (AMM, Orderbook, Settlement)
|
|
636
|
+
|
|
637
|
+
Core pattern: total value across accounts must remain constant.
|
|
638
|
+
|
|
639
|
+
```v
|
|
640
|
+
pub swap(
|
|
641
|
+
pool_a: PoolAccount @mut,
|
|
642
|
+
pool_b: PoolAccount @mut,
|
|
643
|
+
user_a: UserAccount @mut,
|
|
644
|
+
user_b: UserAccount @mut,
|
|
645
|
+
trader: account @signer,
|
|
646
|
+
amount_in: u64
|
|
647
|
+
) {
|
|
648
|
+
require(user_a.owner == trader.key);
|
|
649
|
+
require(user_a.balance >= amount_in);
|
|
650
|
+
require(amount_in > 0);
|
|
651
|
+
let amount_out = (pool_b.reserve * amount_in) / (pool_a.reserve + amount_in);
|
|
652
|
+
require(amount_out > 0);
|
|
653
|
+
user_a.balance = user_a.balance - amount_in;
|
|
654
|
+
pool_a.reserve = pool_a.reserve + amount_in;
|
|
655
|
+
pool_b.reserve = pool_b.reserve - amount_out;
|
|
656
|
+
user_b.balance = user_b.balance + amount_out;
|
|
657
|
+
}
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
**Key ingredients:** `let` with computed expression, multi-account mutation, balance checks on both sides.
|
|
661
|
+
|
|
662
|
+
### 10.7 Threshold & Risk Checks (Lending, Collateral, Liquidation)
|
|
663
|
+
|
|
664
|
+
Core pattern: actions gated by ratio or threshold comparisons.
|
|
665
|
+
|
|
666
|
+
```v
|
|
667
|
+
pub borrow(
|
|
668
|
+
position: LoanPosition @mut,
|
|
669
|
+
borrower: account @signer,
|
|
670
|
+
collateral_value: u64,
|
|
671
|
+
borrow_amount: u64
|
|
672
|
+
) {
|
|
673
|
+
require(position.owner == borrower.key);
|
|
674
|
+
require(borrow_amount > 0);
|
|
675
|
+
// Enforce 150% collateral ratio: collateral * 100 >= total_debt * 150
|
|
676
|
+
let new_debt = position.debt + borrow_amount;
|
|
677
|
+
require(collateral_value * 100 >= new_debt * 150);
|
|
678
|
+
position.debt = new_debt;
|
|
679
|
+
}
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
**Key ingredients:** `let` for intermediate computation, integer math for ratio checks, compound conditions.
|
|
683
|
+
|
|
684
|
+
### 10.8 External Program Integration (CPI)
|
|
685
|
+
|
|
686
|
+
Core pattern: call external Solana programs from within your contract via interfaces.
|
|
687
|
+
|
|
688
|
+
```v
|
|
689
|
+
// Non-Anchor program (bincode, manual discriminators)
|
|
690
|
+
interface ExternalProgram @program("ExternalProgramID111111111111111111111111111") {
|
|
691
|
+
process @discriminator(1) (
|
|
692
|
+
state: Account,
|
|
693
|
+
authority: Account,
|
|
694
|
+
amount: u64
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// Anchor program (borsh, auto discriminators)
|
|
699
|
+
interface AnchorProgram @anchor @program("AnchorProgramID11111111111111111111111111111") {
|
|
700
|
+
execute( // discriminator auto-generated
|
|
701
|
+
config: Account,
|
|
702
|
+
user: Account,
|
|
703
|
+
value: u64
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
pub perform_action(
|
|
708
|
+
local_state: MyState @mut,
|
|
709
|
+
external_account: account @mut,
|
|
710
|
+
user: account @signer,
|
|
711
|
+
amount: u64
|
|
712
|
+
) {
|
|
713
|
+
require(local_state.authority == user.key);
|
|
714
|
+
require(amount > 0);
|
|
715
|
+
|
|
716
|
+
// CPI to external program
|
|
717
|
+
ExternalProgram.process(external_account, user, amount);
|
|
718
|
+
|
|
719
|
+
// Update local state after CPI
|
|
720
|
+
local_state.last_amount = amount;
|
|
721
|
+
local_state.call_count = local_state.call_count + 1;
|
|
722
|
+
}
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
**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.
|
|
726
|
+
|
|
727
|
+
## 11) Mainnet Safety Policy
|
|
728
|
+
|
|
729
|
+
Required preflight gates:
|
|
730
|
+
1. Freeze artifact hash
|
|
731
|
+
2. Lock target/program-id/RPC/key source
|
|
732
|
+
3. Validate key custody
|
|
733
|
+
4. Run simulation/dry-run path
|
|
734
|
+
5. Predefine rollback/containment actions
|
|
735
|
+
|
|
736
|
+
Post-deploy:
|
|
737
|
+
1. smoke execute
|
|
738
|
+
2. confirmed tx validation
|
|
739
|
+
3. CU baseline capture
|
|
740
|
+
4. incident process if anomalies appear
|
|
741
|
+
|
|
742
|
+
## 12) Common Failure Signatures
|
|
743
|
+
|
|
744
|
+
1. `No program ID resolved for Five VM`:
|
|
745
|
+
- set explicit program-id source
|
|
746
|
+
2. `Function '<name>' not found in ABI`:
|
|
747
|
+
- use exact ABI name (including namespace)
|
|
748
|
+
3. `Missing required account/argument`:
|
|
749
|
+
- satisfy `.accounts(...)` and `.args(...)`
|
|
750
|
+
4. owner/program mismatch:
|
|
751
|
+
- verify target program ownership assumptions
|
|
752
|
+
5. CPI mismatch:
|
|
753
|
+
- verify explicit serializer/discriminator/account order
|
|
754
|
+
|
|
755
|
+
## 13) Definition of Done
|
|
756
|
+
|
|
757
|
+
Complete means:
|
|
758
|
+
1. `.five` artifact produced
|
|
759
|
+
2. tests passed with evidence
|
|
760
|
+
3. deployment confirmed (if in scope)
|
|
761
|
+
4. execution confirmed with `meta.err == null` (if in scope)
|
|
762
|
+
5. signatures + CU metrics recorded
|
|
763
|
+
6. integration snippet delivered (SDK/frontend when requested)
|
|
764
|
+
|
|
765
|
+
## 14) Agent Behavior Rules
|
|
766
|
+
|
|
767
|
+
1. Prefer deterministic, minimal command paths.
|
|
768
|
+
2. Verify tx outcomes; do not assume send success == execution success.
|
|
769
|
+
3. Avoid hidden defaults for deploy/CPI critical parameters.
|
|
770
|
+
4. Keep changes auditable and reproducible.
|
|
771
|
+
5. If uncertain, inspect compiler/CLI source directly.
|
|
772
|
+
|
|
773
|
+
## 15) Agent One-Shot Contract Generation Procedure
|
|
774
|
+
|
|
775
|
+
Follow this procedure to produce correct 5IVE contracts on first compilation, regardless of contract type.
|
|
776
|
+
|
|
777
|
+
### Step 1: Define account schemas
|
|
778
|
+
- Identify every distinct on-chain state object your contract needs.
|
|
779
|
+
- Each gets an `account Name { ... }` block with **all fields terminated by `;`**.
|
|
780
|
+
- Choose field types from: `pubkey`, `u8`–`u128`, `i8`–`i64`, `bool`, `string<N>`.
|
|
781
|
+
- Include an `authority: pubkey;` field on any account that needs access control.
|
|
782
|
+
- Include a `status: u8;` field for lifecycle/state-machine accounts.
|
|
783
|
+
|
|
784
|
+
### Step 2: Define initializer functions
|
|
785
|
+
- For each account that users create at runtime, write an `init_*` function.
|
|
786
|
+
- The account parameter uses the full attribute stack: `Type @mut @init(payer=name, space=bytes) @signer`.
|
|
787
|
+
- The payer is `account @mut @signer`.
|
|
788
|
+
- Set every field to a known value (don't leave uninitialized fields).
|
|
789
|
+
- Return `-> pubkey` with `return account.key;` when callers need the address.
|
|
790
|
+
|
|
791
|
+
### Step 3: Define interfaces (if calling external programs)
|
|
792
|
+
- Declare `interface Name @program("...") { ... }` at the top of your file.
|
|
793
|
+
- Each method gets `@discriminator(N)` as a single u8 inline: `method @discriminator(N) (...)`.
|
|
794
|
+
- Use `Account` type for on-chain account params, scalar types for data params.
|
|
795
|
+
- **Do not** add `@serializer(...)` — bincode is the default.
|
|
796
|
+
|
|
797
|
+
### Step 4: Define action functions
|
|
798
|
+
- Every state-mutating function takes the relevant account(s) as `AccountType @mut`.
|
|
799
|
+
- Authorization: take an `account @signer` parameter, then `require(state.authority == signer.key);`.
|
|
800
|
+
- Guards: use `require()` with any comparison operator (`==`, `!=`, `<`, `<=`, `>`, `>=`, `!`).
|
|
801
|
+
- For balance operations: always check `require(source.balance >= amount);` before subtraction.
|
|
802
|
+
- For state machines: check `require(state.status == EXPECTED_STATUS);` before transition.
|
|
803
|
+
- Use `let` for intermediate computations (type inference handles it).
|
|
804
|
+
- For CPI calls: pass `account`-typed params directly (not `.key`), use dot notation `Interface.method(...)`.
|
|
805
|
+
|
|
806
|
+
### Step 5: Define read/query functions
|
|
807
|
+
- Use `-> ReturnType` syntax for functions that return values.
|
|
808
|
+
- `return state.field;` to return account data.
|
|
809
|
+
|
|
810
|
+
### Step 6: Compile and verify
|
|
811
|
+
- Run `5ive build` or `5ive compile src/main.v -o build/main.five`.
|
|
812
|
+
- Fix any parser errors (most common: missing `;` in account fields).
|
|
813
|
+
|
|
814
|
+
### Syntax quick-reference
|
|
815
|
+
|
|
816
|
+
| Pattern | Syntax |
|
|
817
|
+
|---|---|
|
|
818
|
+
| Account field | `name: type;` (semicolon required) |
|
|
819
|
+
| Init parameter | `acc: Type @mut @init(payer=p, space=N) @signer` |
|
|
820
|
+
| Signer parameter | `caller: account @signer` or `caller: account @mut @signer` |
|
|
821
|
+
| Ownership check | `require(state.authority == caller.key);` |
|
|
822
|
+
| Balance guard | `require(state.balance >= amount);` |
|
|
823
|
+
| Boolean guard | `require(!state.is_locked);` |
|
|
824
|
+
| Zero-amount guard | `require(amount > 0);` |
|
|
825
|
+
| Revoke authority | `state.authority = 0;` |
|
|
826
|
+
| Status transition | `state.status = 1;` |
|
|
827
|
+
| Local variable (immutable) | `let x = expr;` |
|
|
828
|
+
| Local variable (mutable) | `let mut x = expr;` (required for reassignment) |
|
|
829
|
+
| Reassign local variable | `let mut x: u64 = 0; ... x = new_value;` |
|
|
830
|
+
| Return value | `pub fn(...) -> u64 { return state.value; }` |
|
|
831
|
+
| Fixed string field | `name: string<32>;` |
|
|
832
|
+
| Zero-init pubkey | `state.delegate = 0;` or `state.delegate = pubkey(0);` |
|
|
833
|
+
| Interface decl | `interface Name @program("...") { ... }` |
|
|
834
|
+
| Interface method | `method @discriminator(N) (param: Account, val: u64);` |
|
|
835
|
+
| CPI call | `InterfaceName.method(acct_param, value);` |
|
|
836
|
+
| CPI account param | `name: account @mut` (not `pubkey`) |
|
|
837
|
+
|
|
838
|
+
## 16) Reference Implementations
|
|
839
|
+
|
|
840
|
+
Three verified, compilable patterns covering distinct contract archetypes. Use as canonical references.
|
|
841
|
+
|
|
842
|
+
### 16.1 Token (Supply Accounting + Delegation + Freeze)
|
|
843
|
+
|
|
844
|
+
```v
|
|
845
|
+
account Mint {
|
|
846
|
+
authority: pubkey;
|
|
847
|
+
freeze_authority: pubkey;
|
|
848
|
+
supply: u64;
|
|
849
|
+
decimals: u8;
|
|
850
|
+
name: string<32>;
|
|
851
|
+
symbol: string<32>;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
account TokenAccount {
|
|
855
|
+
owner: pubkey;
|
|
856
|
+
mint: pubkey;
|
|
857
|
+
balance: u64;
|
|
858
|
+
is_frozen: bool;
|
|
859
|
+
delegate: pubkey;
|
|
860
|
+
delegated_amount: u64;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
pub init_mint(
|
|
864
|
+
mint_account: Mint @mut @init(payer=authority, space=256) @signer,
|
|
865
|
+
authority: account @mut @signer,
|
|
866
|
+
freeze_authority: pubkey,
|
|
867
|
+
decimals: u8,
|
|
868
|
+
name: string<32>,
|
|
869
|
+
symbol: string<32>
|
|
870
|
+
) -> pubkey {
|
|
871
|
+
require(decimals <= 20);
|
|
872
|
+
mint_account.authority = authority.key;
|
|
873
|
+
mint_account.freeze_authority = freeze_authority;
|
|
874
|
+
mint_account.supply = 0;
|
|
875
|
+
mint_account.decimals = decimals;
|
|
876
|
+
mint_account.name = name;
|
|
877
|
+
mint_account.symbol = symbol;
|
|
878
|
+
return mint_account.key;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
pub transfer(
|
|
882
|
+
source: TokenAccount @mut,
|
|
883
|
+
destination: TokenAccount @mut,
|
|
884
|
+
owner: account @signer,
|
|
885
|
+
amount: u64
|
|
886
|
+
) {
|
|
887
|
+
require(source.owner == owner.key);
|
|
888
|
+
require(source.balance >= amount);
|
|
889
|
+
require(source.mint == destination.mint);
|
|
890
|
+
require(!source.is_frozen);
|
|
891
|
+
require(!destination.is_frozen);
|
|
892
|
+
require(amount > 0);
|
|
893
|
+
source.balance = source.balance - amount;
|
|
894
|
+
destination.balance = destination.balance + amount;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
pub approve(
|
|
898
|
+
source: TokenAccount @mut,
|
|
899
|
+
owner: account @signer,
|
|
900
|
+
delegate: pubkey,
|
|
901
|
+
amount: u64
|
|
902
|
+
) {
|
|
903
|
+
require(source.owner == owner.key);
|
|
904
|
+
source.delegate = delegate;
|
|
905
|
+
source.delegated_amount = amount;
|
|
906
|
+
}
|
|
907
|
+
```
|
|
908
|
+
|
|
909
|
+
**Patterns exercised:** `@init` stacking, supply accounting, freeze guards, delegation, `.key` extraction, `string<N>`, `-> pubkey` return.
|
|
910
|
+
|
|
911
|
+
### 16.2 Vault (Custody + Authority Gating)
|
|
912
|
+
|
|
913
|
+
```v
|
|
914
|
+
account Vault {
|
|
915
|
+
authority: pubkey;
|
|
916
|
+
balance: u64;
|
|
917
|
+
is_locked: bool;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
pub init_vault(
|
|
921
|
+
vault: Vault @mut @init(payer=creator, space=128) @signer,
|
|
922
|
+
creator: account @mut @signer
|
|
923
|
+
) -> pubkey {
|
|
924
|
+
vault.authority = creator.key;
|
|
925
|
+
vault.balance = 0;
|
|
926
|
+
vault.is_locked = false;
|
|
927
|
+
return vault.key;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
pub deposit(
|
|
931
|
+
vault: Vault @mut,
|
|
932
|
+
depositor: account @signer,
|
|
933
|
+
amount: u64
|
|
934
|
+
) {
|
|
935
|
+
require(!vault.is_locked);
|
|
936
|
+
require(amount > 0);
|
|
937
|
+
vault.balance = vault.balance + amount;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
pub withdraw(
|
|
941
|
+
vault: Vault @mut,
|
|
942
|
+
authority: account @signer,
|
|
943
|
+
amount: u64
|
|
944
|
+
) {
|
|
945
|
+
require(vault.authority == authority.key);
|
|
946
|
+
require(!vault.is_locked);
|
|
947
|
+
require(vault.balance >= amount);
|
|
948
|
+
require(amount > 0);
|
|
949
|
+
vault.balance = vault.balance - amount;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
pub lock_vault(
|
|
953
|
+
vault: Vault @mut,
|
|
954
|
+
authority: account @signer
|
|
955
|
+
) {
|
|
956
|
+
require(vault.authority == authority.key);
|
|
957
|
+
vault.is_locked = true;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
pub transfer_authority(
|
|
961
|
+
vault: Vault @mut,
|
|
962
|
+
current_authority: account @signer,
|
|
963
|
+
new_authority: pubkey
|
|
964
|
+
) {
|
|
965
|
+
require(vault.authority == current_authority.key);
|
|
966
|
+
vault.authority = new_authority;
|
|
967
|
+
}
|
|
968
|
+
```
|
|
969
|
+
|
|
970
|
+
**Patterns exercised:** authority gating, boolean lock, balance guards, authority transfer, `@init` stacking.
|
|
971
|
+
|
|
972
|
+
### 16.3 Escrow (Lifecycle State Machine + Dual-Party Auth)
|
|
973
|
+
|
|
974
|
+
```v
|
|
975
|
+
account Escrow {
|
|
976
|
+
seller: pubkey;
|
|
977
|
+
buyer: pubkey;
|
|
978
|
+
amount: u64;
|
|
979
|
+
status: u8;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
pub create_escrow(
|
|
983
|
+
escrow: Escrow @mut @init(payer=seller, space=128) @signer,
|
|
984
|
+
seller: account @mut @signer,
|
|
985
|
+
buyer: pubkey,
|
|
986
|
+
amount: u64
|
|
987
|
+
) -> pubkey {
|
|
988
|
+
require(amount > 0);
|
|
989
|
+
escrow.seller = seller.key;
|
|
990
|
+
escrow.buyer = buyer;
|
|
991
|
+
escrow.amount = amount;
|
|
992
|
+
escrow.status = 0;
|
|
993
|
+
return escrow.key;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
pub fund_escrow(
|
|
997
|
+
escrow: Escrow @mut,
|
|
998
|
+
buyer: account @signer,
|
|
999
|
+
amount: u64
|
|
1000
|
+
) {
|
|
1001
|
+
require(escrow.buyer == buyer.key);
|
|
1002
|
+
require(escrow.status == 0);
|
|
1003
|
+
require(amount == escrow.amount);
|
|
1004
|
+
escrow.status = 1;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
pub release(
|
|
1008
|
+
escrow: Escrow @mut,
|
|
1009
|
+
buyer: account @signer
|
|
1010
|
+
) {
|
|
1011
|
+
require(escrow.buyer == buyer.key);
|
|
1012
|
+
require(escrow.status == 1);
|
|
1013
|
+
escrow.status = 2;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
pub cancel(
|
|
1017
|
+
escrow: Escrow @mut,
|
|
1018
|
+
seller: account @signer
|
|
1019
|
+
) {
|
|
1020
|
+
require(escrow.seller == seller.key);
|
|
1021
|
+
require(escrow.status == 0);
|
|
1022
|
+
escrow.status = 3;
|
|
1023
|
+
}
|
|
1024
|
+
```
|
|
1025
|
+
|
|
1026
|
+
**Patterns exercised:** integer status for state machine, dual-party authorization, lifecycle transitions, exact-amount matching.
|
|
1027
|
+
|
|
1028
|
+
### 16.4 CPI to External Program (Interface + Cross-Program Calls)
|
|
1029
|
+
|
|
1030
|
+
```v
|
|
1031
|
+
// Interface for external program (non-Anchor, bincode)
|
|
1032
|
+
interface ExternalProgram @program("ExternalProgramID111111111111111111111111111") {
|
|
1033
|
+
update_value @discriminator(5) (
|
|
1034
|
+
state: Account,
|
|
1035
|
+
authority: Account,
|
|
1036
|
+
new_value: u64
|
|
1037
|
+
);
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// Interface for Anchor program (borsh, auto discriminators)
|
|
1041
|
+
interface AnchorProgram @anchor @program("AnchorProgramID11111111111111111111111111111") {
|
|
1042
|
+
process( // discriminator auto-generated from method name
|
|
1043
|
+
config: Account,
|
|
1044
|
+
user: Account,
|
|
1045
|
+
amount: u64
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
account Controller {
|
|
1050
|
+
authority: pubkey;
|
|
1051
|
+
counter: u64;
|
|
1052
|
+
last_value: u64;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
pub init_controller(
|
|
1056
|
+
controller: Controller @mut @init(payer=creator, space=128) @signer,
|
|
1057
|
+
creator: account @mut @signer
|
|
1058
|
+
) -> pubkey {
|
|
1059
|
+
controller.authority = creator.key;
|
|
1060
|
+
controller.counter = 0;
|
|
1061
|
+
controller.last_value = 0;
|
|
1062
|
+
return controller.key;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
pub call_external(
|
|
1066
|
+
controller: Controller @mut,
|
|
1067
|
+
external_state: account @mut,
|
|
1068
|
+
authority: account @signer,
|
|
1069
|
+
value: u64
|
|
1070
|
+
) {
|
|
1071
|
+
require(controller.authority == authority.key);
|
|
1072
|
+
require(value > 0);
|
|
1073
|
+
|
|
1074
|
+
// CPI to external program
|
|
1075
|
+
ExternalProgram.update_value(external_state, authority, value);
|
|
1076
|
+
|
|
1077
|
+
// Update local state
|
|
1078
|
+
controller.counter = controller.counter + 1;
|
|
1079
|
+
controller.last_value = value;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
pub call_anchor(
|
|
1083
|
+
controller: Controller @mut,
|
|
1084
|
+
anchor_config: account @mut,
|
|
1085
|
+
user: account @signer,
|
|
1086
|
+
amount: u64
|
|
1087
|
+
) {
|
|
1088
|
+
require(controller.authority == user.key);
|
|
1089
|
+
|
|
1090
|
+
// CPI to Anchor program
|
|
1091
|
+
AnchorProgram.process(anchor_config, user, amount);
|
|
1092
|
+
|
|
1093
|
+
controller.counter = controller.counter + 1;
|
|
1094
|
+
}
|
|
1095
|
+
```
|
|
1096
|
+
|
|
1097
|
+
**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.
|