@rootzero/contracts 1.9.0 → 1.11.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 (63) hide show
  1. package/CHANGELOG.md +40 -2
  2. package/Core.sol +3 -1
  3. package/Endpoints.sol +3 -6
  4. package/Events.sol +3 -6
  5. package/README.md +35 -33
  6. package/Utils.sol +2 -1
  7. package/blocks/Cursors.sol +39 -88
  8. package/blocks/Keys.sol +14 -4
  9. package/blocks/Schema.sol +49 -44
  10. package/commands/Base.sol +43 -13
  11. package/commands/Burn.sol +3 -6
  12. package/commands/Credit.sol +9 -6
  13. package/commands/Debit.sol +15 -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 +16 -10
  18. package/commands/Relay.sol +21 -12
  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 +7 -10
  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/Unauthorize.sol +7 -10
  30. package/core/Endpoint.sol +153 -0
  31. package/core/Payable.sol +7 -0
  32. package/core/Pipeline.sol +1 -1
  33. package/core/Portal.sol +19 -48
  34. package/docs/Schema.md +114 -82
  35. package/events/Endpoint.sol +19 -0
  36. package/events/Recovered.sol +17 -0
  37. package/events/Schema.sol +23 -0
  38. package/guards/Base.sol +17 -8
  39. package/guards/Revoke.sol +4 -6
  40. package/package.json +1 -1
  41. package/ports/AllowAssets.sol +6 -9
  42. package/ports/Allowance.sol +6 -9
  43. package/ports/Base.sol +21 -8
  44. package/ports/Credit.sol +6 -9
  45. package/ports/Debit.sol +6 -9
  46. package/ports/DenyAssets.sol +6 -9
  47. package/ports/Dispatch.sol +7 -10
  48. package/ports/Pipe.sol +4 -7
  49. package/ports/Redeem.sol +4 -7
  50. package/ports/Settle.sol +6 -9
  51. package/queries/Assets.sol +9 -12
  52. package/queries/Balances.sol +7 -9
  53. package/queries/Base.sol +19 -11
  54. package/utils/Selectors.sol +49 -0
  55. package/commands/admin/Destroy.sol +0 -43
  56. package/commands/admin/Init.sol +0 -43
  57. package/events/Admin.sol +0 -32
  58. package/events/Command.sol +0 -32
  59. package/events/Guard.sol +0 -18
  60. package/events/Port.sol +0 -22
  61. package/events/Query.sol +0 -20
  62. package/events/Resolved.sol +0 -17
  63. package/queries/Positions.sol +0 -54
package/core/Payable.sol CHANGED
@@ -2,6 +2,7 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import {Budget, Values} from "../utils/Value.sol";
5
+ import {max128} from "../utils/Utils.sol";
5
6
 
6
7
  /// @title Payable
7
8
  /// @notice Abstract mixin for entrypoints that accept native value (`msg.value`).
@@ -16,6 +17,12 @@ abstract contract Payable {
16
17
  return Budget({remaining: msg.value});
17
18
  }
18
19
 
20
+ /// @notice Return the current call's native value as a checked uint128.
21
+ /// @return value Current `msg.value` in wei.
22
+ function msgValue() internal view returns (uint128 value) {
23
+ return uint128(max128(msg.value));
24
+ }
25
+
19
26
  /// @notice Deduct the EVM value lane from a packed resource word and return it.
20
27
  /// @dev EVM resources use the low 128 bits as native value/endowment.
21
28
  /// @param budget Mutable budget to deduct from.
package/core/Pipeline.sol CHANGED
@@ -43,7 +43,7 @@ abstract contract Pipeline is Payable {
43
43
  bytes calldata steps,
44
44
  Budget memory budget
45
45
  ) internal {
46
- (Cur memory input, , ) = Cursors.init(steps, 1);
46
+ (Cur memory input, ) = Cursors.init(steps, 1);
47
47
 
48
48
  while (input.i < input.len) {
49
49
  (uint target, uint resources, bytes calldata request) = input.unpackStep();
package/core/Portal.sol CHANGED
@@ -2,67 +2,38 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import {PortCalls} from "./Calls.sol";
5
- import {ResolvedEvent} from "../events/Resolved.sol";
6
- import {UndeliveredEvent} from "../events/Undelivered.sol";
7
- import {Budget} from "../utils/Value.sol";
8
-
9
- abstract contract RoutePayableHook {
10
- /// @notice Override to route an encoded payload through `portal`.
11
- /// @param portal Destination portal identifier, often the destination host ID.
12
- /// @param resources Chain-specific destination resources. EVM adapters
13
- /// may interpret this as packed execution gas and destination value.
14
- /// @param payload Encoded payload ready for the transport layer.
15
- /// @param budget Source native-value budget available for transport
16
- /// fees and destination resource funding.
17
- function route(uint portal, uint resources, bytes memory payload, Budget memory budget) internal virtual;
18
- }
19
-
20
- abstract contract RecoverHook {
21
- /// @notice Override to recover a previously undelivered witness through `handler`.
22
- /// @param handler Port that should attempt recovery.
23
- /// @param key Recovery lookup key.
24
- /// @param witness Witness payload used to prove and replay recovery.
25
- /// @param value Native EVM value assigned to the recovery attempt.
26
- function recover(uint handler, bytes32 key, bytes calldata witness, uint128 value) internal virtual;
27
- }
28
5
 
29
6
  /// @title Portal
30
7
  /// @notice Base contract for hosts that route payloads through portal adapters.
31
- abstract contract Portal is PortCalls, RecoverHook, ResolvedEvent, UndeliveredEvent {
8
+ abstract contract Portal is PortCalls {
32
9
  error BadWitness();
33
10
 
34
- /// @dev Remote port used to handle messages delivered through this portal.
35
- uint private immutable port;
36
-
37
11
  mapping(bytes32 key => bytes32 digest) internal undelivered;
38
12
 
39
- /// @param handler Remote port used to handle messages delivered through this portal.
40
- constructor(uint handler) {
41
- port = handler;
13
+ /// @notice Try to forward `message` to `port`.
14
+ /// @dev Records and returns `keccak256(message)` under `key` only when forwarding fails.
15
+ /// @param port Port that should handle the forwarded message.
16
+ /// @param key Forwarding/recovery lookup key.
17
+ /// @param message Encoded port input to forward.
18
+ /// @param value Native EVM value assigned to the forwarding attempt.
19
+ /// @return miss Message digest recorded for recovery when forwarding fails; zero on success.
20
+ function forward(uint port, bytes32 key, bytes calldata message, uint128 value) internal returns (bytes32 miss) {
21
+ if (tryCallPort(port, value, message)) return bytes32(0);
22
+
23
+ miss = keccak256(message);
24
+ undelivered[key] = miss;
42
25
  }
43
26
 
44
- /// @notice Try to deliver `message` to this portal's handler port.
45
- /// @dev Records `keccak256(message)` under `key` only when delivery fails.
46
- /// @param key Delivery/recovery lookup key.
47
- /// @param message Encoded port input to deliver.
48
- /// @param value Native EVM value assigned to the delivery attempt.
49
- function deliver(bytes32 key, bytes calldata message, uint128 value) internal {
50
- if (tryCallPort(port, value, message)) return;
51
- bytes32 digest = keccak256(message);
52
- undelivered[key] = digest;
53
- emit Undelivered(host, key, digest);
54
- }
55
-
56
- /// @notice Recover a previously undelivered witness through `handler`.
27
+ /// @notice Retry a previously undelivered witness through `port`.
57
28
  /// @dev The witness must hash to the digest stored under `key`.
58
- /// @param handler Port that should attempt recovery.
29
+ /// @param port Port that should attempt recovery.
59
30
  /// @param key Recovery lookup key.
60
31
  /// @param witness Witness payload used to prove and replay recovery.
61
32
  /// @param value Native EVM value assigned to the recovery attempt.
62
- function recover(uint handler, bytes32 key, bytes calldata witness, uint128 value) internal virtual override {
63
- if (keccak256(witness) != undelivered[key]) revert BadWitness();
33
+ function retry(uint port, bytes32 key, bytes calldata witness, uint128 value) internal virtual {
34
+ if (undelivered[key] != keccak256(witness)) revert BadWitness();
35
+
64
36
  delete undelivered[key];
65
- callPort(handler, value, witness);
66
- emit Resolved(host, key);
37
+ callPort(port, value, witness);
67
38
  }
68
39
  }
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,39 +17,50 @@ 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(1)`, 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
@@ -56,8 +69,8 @@ A block payload has fixed fields first, followed by an optional child-block tail
56
69
  Once a child block appears, no more fixed fields may follow.
57
70
 
58
71
  ```txt
59
- #call { uint target, uint resources, #bytes as payload }
60
- #context { bytes32 account, #bytes as state, #bytes as request }
72
+ { uint target, uint resources, #bytes as payload }
73
+ { bytes32 account, #bytes as state, #bytes as request }
61
74
  ```
62
75
 
63
76
  The tail is embedded directly as child block bytes. There is no wrapper around a
@@ -75,59 +88,87 @@ alias to give those bytes a presentation name:
75
88
  Cardinality is expressed with prefix keywords:
76
89
 
77
90
  ```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 }
91
+ #balance
92
+ maybe #balance
93
+ many #balance
94
+ maybe many #balance
82
95
  ```
83
96
 
84
97
  - no prefix: one required item
85
98
  - `maybe`: optional item
86
- - `many`: one `#list` block whose payload contains repeated items
99
+ - `many`: one generic `#list` block whose payload contains repeated items
87
100
  - `maybe many`: optional `#list` block
88
101
 
89
102
  `maybe` emits no placeholder when absent. `many` wraps repeated items in one
90
103
  generic list block; it does not repeat the item in place.
91
104
 
92
- ## Prime Items
105
+ ## Endpoint Lanes
106
+
107
+ Endpoint descriptors identify each lane with a block key and group size. In
108
+ Solidity, endpoint definition helpers accept `bytes9` lane values, with plain
109
+ `bytes4` keys and the `bytes8` values returned by `many(item)` widening
110
+ implicitly. A plain key or `many(item)` stores a zero group byte that readers
111
+ interpret as group size 1, while `bytes9(0)` or `Keys.Empty` means the endpoint
112
+ has no blocks in that lane. Use
113
+ `group(lane, size)` when a lane needs an explicit group size other than 1.
114
+
115
+ The packed descriptor stores each lane key as an 8-byte value:
116
+
117
+ ```txt
118
+ [key bytes4][item bytes4]
119
+ ```
120
+
121
+ A plain block key is widened into `[key][0]`, so normal endpoint declarations can
122
+ pass standard `bytes4` keys directly. A lane with a nonzero `item` describes a
123
+ generic container block: `key` is the top-level wire key and `item` is the
124
+ contained item key. The built-in `many(item)` helper creates `[Keys.List][item]`
125
+ with the default group size 1, matching the DSL form `many #item`.
93
126
 
94
- The empty string `""` means no schema. Whitespace-only schemas are invalid.
127
+ Any non-empty lane resolves its key to a block alias and schema body through the
128
+ active schema context. If the item slot is nonzero, tooling also resolves that
129
+ item key in the same context. A bare list lane, `[Keys.List][0]`, is incomplete
130
+ discovery metadata because it does not say what the list contains; indexers
131
+ should reject it for self-describing endpoints.
95
132
 
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.
133
+ The lane key is the prime item. Prime items may repeat at the top level for
134
+ batching. When the lane is `many #item`, the repeated prime item is the generic
135
+ LIST block and each LIST payload contains repeated `item` blocks. Later
136
+ top-level items are globals for the whole batch and are not counted as
137
+ per-operation prime blocks.
99
138
 
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`.
139
+ The prime item cannot be optional. If an endpoint needs a per-operation marker
140
+ with no payload, use a zero-payload block such as `#unit`.
102
141
 
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.
142
+ Endpoint descriptors currently use a narrower convention than the full block
143
+ grammar: each state, input, or output lane is a single run of blocks, without
144
+ additional global items. Future protocol surfaces may use the more flexible
145
+ top-level structure.
107
146
 
108
- ## Aliases
147
+ ## Field Aliases
109
148
 
110
- Aliases are presentation metadata for tooling. They do not change payload layout
111
- or runtime keys.
149
+ Block aliases are published in `Schema` events. Field aliases are presentation
150
+ metadata for tooling. They do not change payload layout or runtime keys.
112
151
 
113
152
  ```txt
114
- maybe #account { bytes32 account } as recipient
115
- #call { uint target, uint resources, #bytes as payload }
153
+ maybe #account as recipient
154
+ { uint target, uint resources, #bytes as payload }
116
155
  ```
117
156
 
118
- Aliases may be used on any block item, including child blocks and prime items.
157
+ Field aliases may be used on any block item, including child blocks and prime
158
+ items.
119
159
 
120
- A child block without an inline body may also be used as a schema reference:
160
+ Child blocks are schema references:
121
161
 
122
162
  ```txt
123
- #recover { uint handler, uint resources, bytes32 key, #bytes as witness }
163
+ { uint handler, uint resources, bytes32 key, #bytes as witness }
124
164
  ```
125
165
 
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.
166
+ Alias resolution is context-dependent. A consumer may resolve `#context` from
167
+ standard block events, from app-specific block events, or from another active
168
+ schema context. Custom parents should define nested custom blocks from the
169
+ bottom up and reference them by alias. Consumers should reject schemas with
170
+ unresolved aliases. The runtime encoding is still an embedded child block with
171
+ the referenced key and layout.
131
172
 
132
173
  ## Field Paths
133
174
 
@@ -136,13 +177,13 @@ path does not change the block key, payload bytes, payload length, cursor
136
177
  behavior, or any onchain validation. It is metadata only.
137
178
 
138
179
  ```txt
139
- #dispatch { uint dst.portal, uint dst.resources, #bytes as dst.payload }
180
+ { uint dst.portal, uint dst.resources, #bytes as dst.payload }
140
181
  ```
141
182
 
142
183
  This has the same runtime layout as:
143
184
 
144
185
  ```txt
145
- #dispatch { uint portal, uint resources, #bytes as payload }
186
+ { uint portal, uint resources, #bytes as payload }
146
187
  ```
147
188
 
148
189
  Offchain tooling may decode the dotted form into a nested object:
@@ -169,11 +210,11 @@ uint dst.portal, uint dst.portal // duplicate path
169
210
  uint dst, uint dst.portal // prefix/value collision
170
211
  ```
171
212
 
172
- The same rule applies to block aliases:
213
+ The same rule applies to field aliases:
173
214
 
174
215
  ```txt
175
- #call { uint target, uint resources, #bytes as calldata.payload }
176
- maybe #account { bytes32 account } as recipient.account
216
+ { uint target, uint resources, #bytes as calldata.payload }
217
+ maybe #account as recipient.account
177
218
  ```
178
219
 
179
220
  ## Field Types
@@ -226,8 +267,8 @@ the layout of an ID only apply to structured IDs.
226
267
 
227
268
  ## Identifiers
228
269
 
229
- Block names use lower camelCase ASCII identifiers. Field names and aliases use
230
- one or more lower camelCase path segments separated by dots:
270
+ Block aliases use lower camelCase ASCII identifiers. Field names and aliases
271
+ use one or more lower camelCase path segments separated by dots:
231
272
 
232
273
  ```txt
233
274
  [a-z][a-zA-Z0-9]*
@@ -246,48 +287,39 @@ asset.
246
287
  ```
247
288
 
248
289
  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.
290
+ reserved block aliases `bytes` and `list`. For dotted paths, reserved words are
291
+ invalid in any path segment.
251
292
 
252
293
  ## Reserved Blocks
253
294
 
254
295
  - `#bytes`: raw dynamic bytes, written without a body
255
- - `#data`: generic/custom payload block
256
296
  - `#list`: generic list wrapper emitted by `many`
257
297
 
258
- Use `#data` when a local schema needs a stable generic key:
298
+ Custom input shapes should define their own context-local block key and publish
299
+ that key with a `Schema` event:
259
300
 
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
301
+ ```solidity
302
+ bytes4 constant Input = Keys.local(1);
303
+ emit Schema(host, Input, "{ bytes32 asset, uint amount }", bytes32("payment"));
270
304
  ```
271
305
 
272
- expands to:
273
-
274
- ```txt
275
- #data { uint foo, bytes32 tag }
276
- ```
306
+ The key can be a small literal, a selector, or any other `bytes4` value that is
307
+ unique in the context where it is used. The alias names the block; the schema
308
+ string describes only the payload body.
277
309
 
278
310
  ## Standard Blocks
279
311
 
280
312
  Common protocol schemas live in `contracts/blocks/Schema.sol`:
281
313
 
282
314
  ```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 }
315
+ amount { bytes32 asset, uint amount }
316
+ balance { bytes32 asset, uint amount }
317
+ custody { uint host, bytes32 asset, uint amount }
318
+ call { uint target, uint resources, #bytes as payload }
319
+ step { uint target, uint resources, #bytes as request }
320
+ context { bytes32 account, #bytes as state, #bytes as request }
321
+ recover { uint handler, uint resources, bytes32 key, #bytes as witness }
322
+ auth { uint cid, uint deadline, #bytes as proof }
291
323
  ```
292
324
 
293
- `Keys.sol` contains the corresponding runtime keys.
325
+ `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,17 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import {EventEmitter} from "./Emitter.sol";
5
+
6
+ /// @notice Emitted when a host recovers a previously recorded key.
7
+ abstract contract RecoveredEvent is EventEmitter {
8
+ string private constant ABI = "event Recovered(uint indexed host, bytes32 key)";
9
+
10
+ /// @param host Host node ID that owns the recovered key.
11
+ /// @param key Recovery lookup key.
12
+ event Recovered(uint indexed host, bytes32 key);
13
+
14
+ constructor() {
15
+ emit EventAbi(ABI);
16
+ }
17
+ }
@@ -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.9.0",
3
+ "version": "1.11.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",
@@ -3,7 +3,7 @@ pragma solidity ^0.8.33;
3
3
 
4
4
  import { PortBase } from "./Base.sol";
5
5
  import { AllowAssetsHook } from "../commands/admin/AllowAssets.sol";
6
- import { Cursors, Cur, Schemas } from "../Cursors.sol";
6
+ import { Cursors, Cur, Keys } from "../Cursors.sol";
7
7
 
8
8
  using Cursors for Cur;
9
9
 
@@ -11,25 +11,22 @@ using Cursors for Cur;
11
11
  /// @notice Port that permits a list of assets on behalf of a peer host.
12
12
  /// Each ASSET block in the request calls `allowAsset`. Restricted to trusted peers.
13
13
  abstract contract PortAllowAssets is PortBase, AllowAssetsHook {
14
- uint internal immutable portAllowAssetsId = portId(this.portAllowAssets.selector);
14
+ bytes32 private immutable descriptor;
15
15
 
16
16
  constructor() {
17
- emit Port(host, portAllowAssetsId, "1:0", Schemas.Asset, "", false);
18
- emit Labeled(portAllowAssetsId, bytes32(0), "portAllowAssets");
17
+ (, descriptor) = port("portAllowAssets", Keys.Asset, Keys.Empty, 0, false);
19
18
  }
20
19
 
21
20
  /// @notice Execute the allow-assets peer call.
22
21
  /// @param data ASSET block stream supplied by the trusted peer.
23
22
  /// @return Empty response bytes.
24
23
  function portAllowAssets(bytes calldata data) external onlyPeer returns (bytes memory) {
25
- (Cur memory assets, , ) = Cursors.init(data, 1);
24
+ (Cur memory input, ) = openInput(data, descriptor);
26
25
 
27
- while (assets.i < assets.len) {
28
- bytes32 asset = assets.unpackAsset();
26
+ while (input.i < input.len) {
27
+ bytes32 asset = input.unpackAsset();
29
28
  allowAsset(asset);
30
29
  }
31
-
32
- assets.complete();
33
30
  return "";
34
31
  }
35
32
  }