@rootzero/contracts 1.10.0 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/CHANGELOG.md +46 -1
  2. package/Core.sol +1 -0
  3. package/Endpoints.sol +2 -3
  4. package/Events.sol +2 -5
  5. package/README.md +38 -36
  6. package/Utils.sol +2 -1
  7. package/blocks/Cursors.sol +39 -75
  8. package/blocks/Keys.sol +18 -4
  9. package/blocks/Schema.sol +53 -47
  10. package/commands/Base.sol +43 -13
  11. package/commands/Burn.sol +3 -6
  12. package/commands/Credit.sol +4 -6
  13. package/commands/Debit.sol +10 -11
  14. package/commands/Deposit.sol +19 -23
  15. package/commands/Payout.sol +6 -10
  16. package/commands/Provision.sol +19 -23
  17. package/commands/Recover.sol +7 -9
  18. package/commands/Relay.sol +10 -11
  19. package/commands/Withdraw.sol +3 -6
  20. package/commands/admin/AllowAssets.sol +7 -10
  21. package/commands/admin/Allowance.sol +7 -10
  22. package/commands/admin/Appoint.sol +7 -10
  23. package/commands/admin/Authorize.sol +10 -19
  24. package/commands/admin/Base.sol +1 -2
  25. package/commands/admin/DenyAssets.sol +7 -10
  26. package/commands/admin/Dismiss.sol +7 -10
  27. package/commands/admin/Execute.sol +7 -10
  28. package/commands/admin/Label.sol +8 -11
  29. package/commands/admin/Schemas.sol +31 -0
  30. package/commands/admin/Unauthorize.sol +10 -19
  31. package/core/Endpoint.sol +160 -0
  32. package/core/Host.sol +1 -1
  33. package/docs/Schema.md +124 -86
  34. package/events/Endpoint.sol +19 -0
  35. package/events/Schema.sol +23 -0
  36. package/guards/Base.sol +17 -8
  37. package/guards/Revoke.sol +4 -6
  38. package/package.json +1 -1
  39. package/ports/AllowAssets.sol +6 -9
  40. package/ports/Allowance.sol +6 -9
  41. package/ports/Base.sol +21 -8
  42. package/ports/Credit.sol +6 -9
  43. package/ports/Debit.sol +6 -9
  44. package/ports/DenyAssets.sol +6 -9
  45. package/ports/Dispatch.sol +6 -9
  46. package/ports/Pipe.sol +4 -7
  47. package/ports/Redeem.sol +4 -7
  48. package/ports/Settle.sol +6 -9
  49. package/queries/Assets.sol +9 -12
  50. package/queries/Balances.sol +7 -9
  51. package/queries/Base.sol +19 -11
  52. package/utils/Selectors.sol +49 -0
  53. package/commands/admin/Destroy.sol +0 -43
  54. package/commands/admin/Init.sol +0 -43
  55. package/events/Admin.sol +0 -32
  56. package/events/Command.sol +0 -32
  57. package/events/Guard.sol +0 -18
  58. package/events/Port.sol +0 -22
  59. package/events/Query.sol +0 -20
  60. package/queries/Positions.sol +0 -54
@@ -0,0 +1,160 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import {Cursors, Cur} from "../Cursors.sol";
5
+ import {Keys} from "../blocks/Keys.sol";
6
+ import {EndpointEvent} from "../events/Endpoint.sol";
7
+ import {LabeledEvent} from "../events/Labeled.sol";
8
+ import {SchemaEvent} from "../events/Schema.sol";
9
+ import {Runtime} from "./Runtime.sol";
10
+
11
+ /// @title Lane
12
+ /// @notice Bit offsets for endpoint descriptor lanes.
13
+ library Lane {
14
+ /// @dev Descriptor shift for the state lane.
15
+ uint internal constant State = 184;
16
+ /// @dev Descriptor shift for the input lane.
17
+ uint internal constant Input = 112;
18
+ /// @dev Descriptor shift for the output lane.
19
+ uint internal constant Output = 40;
20
+ }
21
+
22
+ /// @title EndpointBase
23
+ /// @notice Shared endpoint metadata helpers.
24
+ abstract contract EndpointBase is Runtime, EndpointEvent, LabeledEvent, SchemaEvent {
25
+ /// @dev Pack endpoint lanes and flags into a descriptor.
26
+ /// A non-empty lane with group 0 defaults to group 1; a zero lane is absent.
27
+ /// Layout: `[state:8][group:1][input:8][group:1][output:8][group:1]`
28
+ /// `[flags:1][reserved:4]`. Flag bits: funded = 0, admin = 1.
29
+ /// @param state Packed state lane plus optional group byte.
30
+ /// @param input Packed input lane plus optional group byte.
31
+ /// @param output Packed output lane plus optional group byte.
32
+ /// @param funded Whether the endpoint accepts nonzero native value.
33
+ /// @param admin Whether the endpoint is restricted to the admin account.
34
+ /// @return value Packed endpoint descriptor as an integer.
35
+ function pack(
36
+ bytes9 state,
37
+ bytes9 input,
38
+ bytes9 output,
39
+ bool funded,
40
+ bool admin
41
+ ) private pure returns (uint value) {
42
+ value |= uint(uint72(state)) << Lane.State;
43
+ value |= uint(uint72(input)) << Lane.Input;
44
+ value |= uint(uint72(output)) << Lane.Output;
45
+ value |= uint(funded ? 1 : 0) << 32;
46
+ value |= uint(admin ? 1 : 0) << 33;
47
+ }
48
+
49
+ /// @dev Return a lane's effective group size, defaulting non-empty lanes to one.
50
+ /// @param descriptor Packed endpoint descriptor.
51
+ /// @param shift Bit offset of the lane to inspect.
52
+ /// @return size Effective group size, or zero when the lane is absent.
53
+ function laneGroup(bytes32 descriptor, uint shift) private pure returns (uint8 size) {
54
+ uint72 lane = uint72(uint(descriptor) >> shift);
55
+ if (lane == 0) return 0;
56
+
57
+ size = uint8(lane);
58
+ if (size == 0) size = 1;
59
+ }
60
+
61
+ /// @notice Return an 8-byte lane value for a generic LIST containing `item`.
62
+ /// @param item Block key expected inside each LIST payload.
63
+ /// @return Packed lane key `[Keys.List][item]`.
64
+ function many(bytes4 item) internal pure returns (bytes8) {
65
+ return bytes8(bytes.concat(Keys.List, item));
66
+ }
67
+
68
+ /// @notice Append an explicit group size to an 8-byte lane value.
69
+ /// @param value Packed lane key `[key][item]`.
70
+ /// @param size Explicit per-operation group size for the lane.
71
+ /// @return Packed lane key plus group byte.
72
+ function group(bytes8 value, uint8 size) internal pure returns (bytes9) {
73
+ return bytes9(bytes.concat(value, bytes1(size)));
74
+ }
75
+
76
+ /// @dev Open a descriptor lane and return its effective group and output counts.
77
+ /// An absent lane inherits `expected`; a present lane must match it when nonzero.
78
+ /// @param source Block stream to open for the requested lane.
79
+ /// @param descriptor Packed endpoint descriptor.
80
+ /// @param shift Bit offset of the lane to open.
81
+ /// @param expected Required group count, or zero to accept the lane's count.
82
+ /// @return cur Cursor scoped to the lane's first block run.
83
+ /// @return groups Number of lane groups in `cur`, or `expected` for an absent lane.
84
+ /// @return outputs Number of output blocks implied by `groups` and the descriptor output lane.
85
+ function openLane(
86
+ bytes calldata source,
87
+ bytes32 descriptor,
88
+ uint shift,
89
+ uint expected
90
+ ) internal pure returns (Cur memory cur, uint groups, uint outputs) {
91
+ (cur, groups) = Cursors.init(source, laneGroup(descriptor, shift));
92
+ if (groups == 0) groups = expected;
93
+ else if (expected != 0 && groups != expected) revert Cursors.BadRatio();
94
+ outputs = groups * laneGroup(descriptor, Lane.Output);
95
+ }
96
+
97
+ /// @notice Open an endpoint state stream and return the expected output block count.
98
+ /// @param source State block stream to open.
99
+ /// @param descriptor Packed endpoint descriptor.
100
+ /// @return state Cursor scoped to the state lane's first block run.
101
+ /// @return outputs Number of output blocks implied by the state group count.
102
+ function openState(
103
+ bytes calldata source,
104
+ bytes32 descriptor
105
+ ) internal pure returns (Cur memory state, uint outputs) {
106
+ (state, , outputs) = openLane(source, descriptor, Lane.State, 0);
107
+ }
108
+
109
+ /// @notice Open an endpoint input stream and return the expected output block count.
110
+ /// @param source Input block stream to open.
111
+ /// @param descriptor Packed endpoint descriptor.
112
+ /// @return input Cursor scoped to the input lane's first block run.
113
+ /// @return outputs Number of output blocks implied by the input group count.
114
+ function openInput(
115
+ bytes calldata source,
116
+ bytes32 descriptor
117
+ ) internal pure returns (Cur memory input, uint outputs) {
118
+ (input, , outputs) = openLane(source, descriptor, Lane.Input, 0);
119
+ }
120
+
121
+ /// @notice Publish the default local block schema and return `Keys.Local`.
122
+ /// @param body Schema DSL string describing the block payload body.
123
+ /// @return The default context-local block key.
124
+ function localSchema(string memory body) internal returns (bytes4) {
125
+ return localSchema(1, body);
126
+ }
127
+
128
+ /// @notice Publish a context-local block schema and return its key.
129
+ /// @param key Context-local key value.
130
+ /// @param body Schema DSL string describing the block payload body.
131
+ /// @return The context-local block key.
132
+ function localSchema(uint32 key, string memory body) internal returns (bytes4) {
133
+ bytes4 k = Keys.local(key);
134
+ emit Schema(host, k, body, bytes32(0));
135
+ return k;
136
+ }
137
+
138
+ /// @notice Create and publish endpoint metadata with a default label.
139
+ /// @param id Endpoint node ID.
140
+ /// @param name Default human-readable endpoint label.
141
+ /// @param state Packed state lane plus optional group byte.
142
+ /// @param input Packed input lane plus optional group byte.
143
+ /// @param output Packed output lane plus optional group byte.
144
+ /// @param funded Whether the endpoint accepts nonzero native value.
145
+ /// @param admin Whether the endpoint is restricted to the admin account.
146
+ /// @return descriptor Packed endpoint lane metadata and flags.
147
+ function endpoint(
148
+ uint id,
149
+ string memory name,
150
+ bytes9 state,
151
+ bytes9 input,
152
+ bytes9 output,
153
+ bool funded,
154
+ bool admin
155
+ ) internal returns (bytes32 descriptor) {
156
+ descriptor = bytes32(pack(state, input, output, funded, admin));
157
+ emit Endpoint(host, id, descriptor);
158
+ emit Labeled(id, bytes32(0), name);
159
+ }
160
+ }
package/core/Host.sol CHANGED
@@ -23,7 +23,7 @@ interface IHostIntroduction {
23
23
 
24
24
  /// @title Host
25
25
  /// @notice Abstract base contract for rootzero host implementations.
26
- /// Inherits admin command support (authorize, unauthorize, executePayable),
26
+ /// Inherits admin command support (authorize, unauthorize, label, executePayable),
27
27
  /// guardian management, the default guardian revoke action, and
28
28
  /// optionally introduces itself to a commander host at deployment.
29
29
  /// Accepts native ETH payments via the `receive` function.
package/docs/Schema.md CHANGED
@@ -1,8 +1,10 @@
1
1
  # Schema
2
2
 
3
3
  Rootzero request and response data is encoded as a stream of typed blocks. A
4
- schema string describes payload layout for discovery events and tooling; the
5
- runtime block key is derived only from the block name.
4
+ schema string describes the payload body for discovery events and tooling; the
5
+ runtime block key is the compact type tag that identifies that payload layout in
6
+ the active schema context. The block alias is published separately from the
7
+ payload schema.
6
8
 
7
9
  ## Wire Format
8
10
 
@@ -15,53 +17,67 @@ Every block uses the same header:
15
17
  `payloadLen` is big-endian and counts only payload bytes. Child blocks and list
16
18
  items use the same header format.
17
19
 
18
- The block key is:
20
+ Standard built-in block keys use:
19
21
 
20
22
  ```txt
21
23
  bytes4(keccak256("#name"))
22
24
  ```
23
25
 
24
- For example, `#amount { bytes32 asset, uint amount }` uses the key
25
- derived from `#amount`. Blocks must not be overloaded: one block name should have
26
- one protocol meaning.
26
+ For example, the standard `amount` alias uses the key derived from `#amount`
27
+ and the schema body `{ bytes32 asset, uint amount }`. Custom block keys do not
28
+ have to be keccak-derived. They
29
+ are opaque `bytes4` tags and only need to be unique in the context where they are
30
+ used. A host can publish the meaning of a custom key with:
31
+
32
+ ```solidity
33
+ event Schema(uint indexed host, bytes4 key, string schema, bytes32 name);
34
+ ```
35
+
36
+ For example, a host-specific payment block can use `Keys.Local`, the command
37
+ selector, or any other chosen `bytes4` value as long as that key is not
38
+ overloaded in the relevant host/schema context.
27
39
 
28
40
  ## Block Syntax
29
41
 
30
- A block starts with `#`. Fixed fields are written in braces:
42
+ A block definition has an event alias and a schema body. Fixed fields are
43
+ written in braces:
31
44
 
32
45
  ```txt
33
- #amount { bytes32 asset, uint amount }
34
- #account { bytes32 account }
46
+ alias: amount
47
+ schema: { bytes32 asset, uint amount }
35
48
  ```
36
49
 
37
- A block without braces has no payload:
50
+ A block body can reference another block alias as a child item with `#`:
38
51
 
39
52
  ```txt
40
- #unit
41
- #bytes
53
+ { bytes32 account, #bytes as state, #bytes as request }
42
54
  ```
43
55
 
44
- Empty braces are invalid. A zero-payload block must omit braces.
56
+ The empty schema string `""` means the block has no structured payload. This is
57
+ used for zero-payload blocks such as `#unit` and raw dynamic blocks such as
58
+ `#bytes`.
45
59
 
46
- A schema is a comma-separated list of items. Order is significant.
60
+ A schema body is a comma-separated list of items. Order is significant.
47
61
 
48
62
  ```txt
49
- #amount { bytes32 asset, uint amount },
50
- maybe #account { bytes32 account }
63
+ { #amount, maybe #account as recipient }
51
64
  ```
52
65
 
53
66
  ## Payload Layout
54
67
 
55
- A block payload has fixed fields first, followed by an optional child-block tail.
56
- Once a child block appears, no more fixed fields may follow.
68
+ A block payload encodes schema items in declaration order. Fixed fields are
69
+ packed inline, and zero or more child blocks are embedded directly at their
70
+ declared positions. Because every child block carries its own header and payload
71
+ length, fixed fields may appear before, after, or between child blocks.
57
72
 
58
73
  ```txt
59
- #call { uint target, uint resources, #bytes as payload }
60
- #context { bytes32 account, #bytes as state, #bytes as request }
74
+ { uint target, uint resources, #bytes as payload }
75
+ { bytes32 account, #bytes as state, #bytes as request }
76
+ { bytes4 key, #string as body, bytes32 name }
77
+ { #bytes as left, uint op, #bytes as right }
61
78
  ```
62
79
 
63
- The tail is embedded directly as child block bytes. There is no wrapper around a
64
- child-block tail.
80
+ There is no wrapper around embedded child blocks.
65
81
 
66
82
  Raw dynamic bytes are represented with the reserved `#bytes` child block. Use an
67
83
  alias to give those bytes a presentation name:
@@ -75,59 +91,87 @@ alias to give those bytes a presentation name:
75
91
  Cardinality is expressed with prefix keywords:
76
92
 
77
93
  ```txt
78
- #balance { bytes32 asset, uint amount }
79
- maybe #balance { bytes32 asset, uint amount }
80
- many #balance { bytes32 asset, uint amount }
81
- maybe many #balance { bytes32 asset, uint amount }
94
+ #balance
95
+ maybe #balance
96
+ many #balance
97
+ maybe many #balance
82
98
  ```
83
99
 
84
100
  - no prefix: one required item
85
101
  - `maybe`: optional item
86
- - `many`: one `#list` block whose payload contains repeated items
102
+ - `many`: one generic `#list` block whose payload contains repeated items
87
103
  - `maybe many`: optional `#list` block
88
104
 
89
105
  `maybe` emits no placeholder when absent. `many` wraps repeated items in one
90
106
  generic list block; it does not repeat the item in place.
91
107
 
92
- ## Prime Items
108
+ ## Endpoint Lanes
109
+
110
+ Endpoint descriptors identify each lane with a block key and group size. In
111
+ Solidity, endpoint definition helpers accept `bytes9` lane values, with plain
112
+ `bytes4` keys and the `bytes8` values returned by `many(item)` widening
113
+ implicitly. A plain key or `many(item)` stores a zero group byte that readers
114
+ interpret as group size 1, while `bytes9(0)` or `Keys.Empty` means the endpoint
115
+ has no blocks in that lane. Use
116
+ `group(lane, size)` when a lane needs an explicit group size other than 1.
117
+
118
+ The packed descriptor stores each lane key as an 8-byte value:
119
+
120
+ ```txt
121
+ [key bytes4][item bytes4]
122
+ ```
123
+
124
+ A plain block key is widened into `[key][0]`, so normal endpoint declarations can
125
+ pass standard `bytes4` keys directly. A lane with a nonzero `item` describes a
126
+ generic container block: `key` is the top-level wire key and `item` is the
127
+ contained item key. The built-in `many(item)` helper creates `[Keys.List][item]`
128
+ with the default group size 1, matching the DSL form `many #item`.
93
129
 
94
- The empty string `""` means no schema. Whitespace-only schemas are invalid.
130
+ Any non-empty lane resolves its key to a block alias and schema body through the
131
+ active schema context. If the item slot is nonzero, tooling also resolves that
132
+ item key in the same context. A bare list lane, `[Keys.List][0]`, is incomplete
133
+ discovery metadata because it does not say what the list contains; indexers
134
+ should reject it for self-describing endpoints.
95
135
 
96
- For a non-empty schema, the first top-level item is the prime item. Prime items
97
- may repeat at the top level for batching. Later top-level items are globals for
98
- the whole batch and are not counted as per-operation prime blocks.
136
+ The lane key is the prime item. Prime items may repeat at the top level for
137
+ batching. When the lane is `many #item`, the repeated prime item is the generic
138
+ LIST block and each LIST payload contains repeated `item` blocks. Later
139
+ top-level items are globals for the whole batch and are not counted as
140
+ per-operation prime blocks.
99
141
 
100
- The prime item cannot be optional. If a command needs a per-operation marker with
101
- no payload, use a zero-payload block such as `#unit`.
142
+ The prime item cannot be optional. If an endpoint needs a per-operation marker
143
+ with no payload, use a zero-payload block such as `#unit`.
102
144
 
103
- Command request and state streams currently use a narrower convention than the
104
- full block grammar: each is a single run of blocks, without additional global
105
- items. Future protocol surfaces may use the more flexible top-level structure,
106
- but command discovery metadata should describe only that one-run shape.
145
+ Endpoint descriptors currently use a narrower convention than the full block
146
+ grammar: each state, input, or output lane is a single run of blocks, without
147
+ additional global items. Future protocol surfaces may use the more flexible
148
+ top-level structure.
107
149
 
108
- ## Aliases
150
+ ## Field Aliases
109
151
 
110
- Aliases are presentation metadata for tooling. They do not change payload layout
111
- or runtime keys.
152
+ Block aliases are published in `Schema` events. Field aliases are presentation
153
+ metadata for tooling. They do not change payload layout or runtime keys.
112
154
 
113
155
  ```txt
114
- maybe #account { bytes32 account } as recipient
115
- #call { uint target, uint resources, #bytes as payload }
156
+ maybe #account as recipient
157
+ { uint target, uint resources, #bytes as payload }
116
158
  ```
117
159
 
118
- Aliases may be used on any block item, including child blocks and prime items.
160
+ Field aliases may be used on any block item, including child blocks and prime
161
+ items.
119
162
 
120
- A child block without an inline body may also be used as a schema reference:
163
+ Child blocks are schema references:
121
164
 
122
165
  ```txt
123
- #recover { uint handler, uint resources, bytes32 key, #bytes as witness }
166
+ { uint handler, uint resources, bytes32 key, #bytes as witness }
124
167
  ```
125
168
 
126
- Alias resolution is context-dependent. A consumer may resolve `#context` from the
127
- standard `Schemas` table, from app-specific schemas, or from another active
128
- schema context. Consumers should reject schemas with unresolved aliases. The
129
- runtime encoding is still an embedded child block with the referenced key and
130
- layout.
169
+ Alias resolution is context-dependent. A consumer may resolve `#context` from
170
+ standard block events, from app-specific block events, or from another active
171
+ schema context. Custom parents should define nested custom blocks from the
172
+ bottom up and reference them by alias. Consumers should reject schemas with
173
+ unresolved aliases. The runtime encoding is still an embedded child block with
174
+ the referenced key and layout.
131
175
 
132
176
  ## Field Paths
133
177
 
@@ -136,13 +180,13 @@ path does not change the block key, payload bytes, payload length, cursor
136
180
  behavior, or any onchain validation. It is metadata only.
137
181
 
138
182
  ```txt
139
- #dispatch { uint dst.portal, uint dst.resources, #bytes as dst.payload }
183
+ { uint dst.portal, uint dst.resources, #bytes as dst.payload }
140
184
  ```
141
185
 
142
186
  This has the same runtime layout as:
143
187
 
144
188
  ```txt
145
- #dispatch { uint portal, uint resources, #bytes as payload }
189
+ { uint portal, uint resources, #bytes as payload }
146
190
  ```
147
191
 
148
192
  Offchain tooling may decode the dotted form into a nested object:
@@ -169,11 +213,11 @@ uint dst.portal, uint dst.portal // duplicate path
169
213
  uint dst, uint dst.portal // prefix/value collision
170
214
  ```
171
215
 
172
- The same rule applies to block aliases:
216
+ The same rule applies to field aliases:
173
217
 
174
218
  ```txt
175
- #call { uint target, uint resources, #bytes as calldata.payload }
176
- maybe #account { bytes32 account } as recipient.account
219
+ { uint target, uint resources, #bytes as calldata.payload }
220
+ maybe #account as recipient.account
177
221
  ```
178
222
 
179
223
  ## Field Types
@@ -226,8 +270,8 @@ the layout of an ID only apply to structured IDs.
226
270
 
227
271
  ## Identifiers
228
272
 
229
- Block names use lower camelCase ASCII identifiers. Field names and aliases use
230
- one or more lower camelCase path segments separated by dots:
273
+ Block aliases use lower camelCase ASCII identifiers. Field names and aliases
274
+ use one or more lower camelCase path segments separated by dots:
231
275
 
232
276
  ```txt
233
277
  [a-z][a-zA-Z0-9]*
@@ -246,48 +290,42 @@ asset.
246
290
  ```
247
291
 
248
292
  Reserved words include `maybe`, `many`, `as`, all field type names, and the
249
- reserved block names `bytes`, `data`, and `list`. For dotted paths, reserved
250
- words are invalid in any path segment.
293
+ reserved block aliases `bytes` and `list`. For dotted paths, reserved words are
294
+ invalid in any path segment.
251
295
 
252
296
  ## Reserved Blocks
253
297
 
254
298
  - `#bytes`: raw dynamic bytes, written without a body
255
- - `#data`: generic/custom payload block
299
+ - `#string`: UTF-8 string bytes, written without a body
256
300
  - `#list`: generic list wrapper emitted by `many`
257
301
 
258
- Use `#data` when a local schema needs a stable generic key:
302
+ Custom input shapes should define their own context-local block key and publish
303
+ that key with a `Schema` event:
259
304
 
260
- ```txt
261
- #data { uint foo, bytes32 tag }
262
- #data { #bytes as payload }
263
- ```
264
-
265
- If a schema string starts with a fixed field type, it is shorthand for one
266
- top-level `#data` block:
267
-
268
- ```txt
269
- uint foo, bytes32 tag
305
+ ```solidity
306
+ bytes4 constant Input = Keys.Local;
307
+ emit Schema(host, Input, "{ bytes32 asset, uint amount }", bytes32("payment"));
270
308
  ```
271
309
 
272
- expands to:
273
-
274
- ```txt
275
- #data { uint foo, bytes32 tag }
276
- ```
310
+ Use `Keys.local(n)` when a host needs more than one local block key. The key can
311
+ also be a small literal, a selector, or any other `bytes4` value that is unique
312
+ in the context where it is used. The alias names the block; the schema string
313
+ describes only the payload body.
277
314
 
278
315
  ## Standard Blocks
279
316
 
280
317
  Common protocol schemas live in `contracts/blocks/Schema.sol`:
281
318
 
282
319
  ```txt
283
- #amount { bytes32 asset, uint amount }
284
- #balance { bytes32 asset, uint amount }
285
- #custody { uint host, bytes32 asset, uint amount }
286
- #call { uint target, uint resources, #bytes as payload }
287
- #step { uint target, uint resources, #bytes as request }
288
- #context { bytes32 account, #bytes as state, #bytes as request }
289
- #recover { uint handler, uint resources, bytes32 key, #bytes as witness }
290
- #auth { uint cid, uint deadline, #bytes as proof }
320
+ amount { bytes32 asset, uint amount }
321
+ balance { bytes32 asset, uint amount }
322
+ custody { uint host, bytes32 asset, uint amount }
323
+ call { uint target, uint resources, #bytes as payload }
324
+ step { uint target, uint resources, #bytes as request }
325
+ context { bytes32 account, #bytes as state, #bytes as request }
326
+ recover { uint handler, uint resources, bytes32 key, #bytes as witness }
327
+ auth { uint cid, uint deadline, #bytes as proof }
328
+ schema { bytes4 key, #string as body, bytes32 name }
291
329
  ```
292
330
 
293
- `Keys.sol` contains the corresponding runtime keys.
331
+ `Keys.sol` contains the corresponding standard runtime keys.
@@ -0,0 +1,19 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import {EventEmitter} from "./Emitter.sol";
5
+
6
+ /// @title EndpointEvent
7
+ /// @notice Emitted during host deployment to publish a callable endpoint descriptor.
8
+ abstract contract EndpointEvent is EventEmitter {
9
+ string private constant ABI = "event Endpoint(uint indexed host, uint id, bytes32 descriptor)";
10
+
11
+ /// @param host Host node ID that exposes the endpoint.
12
+ /// @param id Endpoint node ID.
13
+ /// @param descriptor Packed endpoint lane metadata and flags.
14
+ event Endpoint(uint indexed host, uint id, bytes32 descriptor);
15
+
16
+ constructor() {
17
+ emit EventAbi(ABI);
18
+ }
19
+ }
@@ -0,0 +1,23 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import {EventEmitter} from "./Emitter.sol";
5
+
6
+ /// @title SchemaEvent
7
+ /// @notice Emitted during host deployment to publish a block key and payload schema.
8
+ /// Block keys are opaque `bytes4` tags. Standard protocol blocks use
9
+ /// keccak-derived keys by convention, but custom block keys only need to be
10
+ /// unique within the publishing host/schema context.
11
+ abstract contract SchemaEvent is EventEmitter {
12
+ string private constant ABI = "event Schema(uint indexed host, bytes4 key, string schema, bytes32 name)";
13
+
14
+ /// @param host Host node ID that publishes this block schema.
15
+ /// @param key Block type key being defined by `host`.
16
+ /// @param schema Schema DSL string describing the block payload body.
17
+ /// @param name Optional block alias used by endpoint descriptors and nested schemas.
18
+ event Schema(uint indexed host, bytes4 key, string schema, bytes32 name);
19
+
20
+ constructor() {
21
+ emit EventAbi(ABI);
22
+ }
23
+ }
package/guards/Base.sol CHANGED
@@ -2,24 +2,33 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import {AccessControl} from "../core/Access.sol";
5
- import {GuardEvent} from "../events/Guard.sol";
6
- import {LabeledEvent} from "../events/Labeled.sol";
5
+ import {EndpointBase} from "../core/Endpoint.sol";
7
6
  import {Nodes} from "../utils/Nodes.sol";
7
+ import {Selectors} from "../utils/Selectors.sol";
8
8
 
9
9
  /// @title GuardBase
10
10
  /// @notice Abstract base for guardian-only direct host actions.
11
11
  /// Guard actions are non-payable direct calls with no command context, state, or response.
12
- abstract contract GuardBase is AccessControl, GuardEvent, LabeledEvent {
12
+ abstract contract GuardBase is AccessControl, EndpointBase {
13
13
  /// @dev Restrict execution to active guardian addresses.
14
14
  modifier onlyGuardian() {
15
15
  if (!isGuardian(msg.sender)) revert AccessDenied();
16
16
  _;
17
17
  }
18
18
 
19
- /// @notice Derive the deterministic node ID for a guard action selector on this contract.
20
- /// @param selector Guard action entrypoint selector.
21
- /// @return Guard action node ID.
22
- function guardId(bytes4 selector) internal view returns (uint) {
23
- return Nodes.toGuard(selector, address(this));
19
+ /// @notice Publish guard metadata and a default label.
20
+ /// @param name Default human-readable guard label and selector name.
21
+ /// @param input Packed input lane plus optional group byte.
22
+ /// @param selector Guard ABI selector, or zero to derive it from `name`.
23
+ /// @return id Guard action node ID.
24
+ /// @return descriptor Packed endpoint lane metadata and flags.
25
+ function guard(
26
+ string memory name,
27
+ bytes9 input,
28
+ bytes4 selector
29
+ ) internal returns (uint id, bytes32 descriptor) {
30
+ selector = selector == bytes4(0) ? Selectors.guard(name) : selector;
31
+ id = Nodes.toGuard(selector, address(this));
32
+ descriptor = endpoint(id, name, bytes9(0), input, bytes9(0), false, false);
24
33
  }
25
34
  }
package/guards/Revoke.sol CHANGED
@@ -2,7 +2,7 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import {GuardBase} from "./Base.sol";
5
- import {Cursors, Cur, Schemas} from "../Cursors.sol";
5
+ import {Cursors, Cur, Keys} from "../Cursors.sol";
6
6
  using Cursors for Cur;
7
7
 
8
8
  /// @title Revoke
@@ -10,21 +10,19 @@ using Cursors for Cur;
10
10
  /// Each NODE block in the request is deauthorized on the host.
11
11
  /// Only callable by active guardian addresses.
12
12
  abstract contract Revoke is GuardBase {
13
- uint internal immutable revokeId = guardId(this.revoke.selector);
13
+ bytes32 private immutable descriptor;
14
14
 
15
15
  constructor() {
16
- emit Guard(host, revokeId, Schemas.Node);
17
- emit Labeled(revokeId, bytes32(0), "revoke");
16
+ (, descriptor) = guard("revoke", Keys.Node, 0);
18
17
  }
19
18
 
20
19
  function revoke(bytes calldata request) external onlyGuardian {
21
- (Cur memory input, ) = Cursors.init(request, 1);
20
+ (Cur memory input, ) = openInput(request, descriptor);
22
21
 
23
22
  while (input.i < input.len) {
24
23
  uint node = input.unpackNode();
25
24
  setNode(node, false);
26
25
  }
27
26
 
28
- input.complete();
29
27
  }
30
28
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rootzero/contracts",
3
- "version": "1.10.0",
3
+ "version": "1.12.0",
4
4
  "description": "Solidity contracts and protocol building blocks for rootzero hosts and commands.",
5
5
  "private": false,
6
6
  "license": "GPL-3.0-only",