@rootzero/contracts 1.14.0 → 1.15.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 (45) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/Core.sol +6 -3
  3. package/Endpoints.sol +1 -2
  4. package/Events.sol +1 -2
  5. package/README.md +36 -7
  6. package/annotations/Action.sol +17 -0
  7. package/annotations/Label.sol +23 -0
  8. package/annotations/Schema.sol +53 -0
  9. package/codec/Blocks.sol +48 -11
  10. package/codec/Decoders.sol +15 -3
  11. package/codec/Keys.sol +6 -2
  12. package/codec/Schema.sol +7 -2
  13. package/codec/Specs.sol +24 -2
  14. package/codec/Writers.sol +2 -4
  15. package/commands/Base.sol +36 -32
  16. package/commands/Burn.sol +6 -2
  17. package/commands/Deposit.sol +10 -4
  18. package/commands/Payout.sol +6 -2
  19. package/commands/Withdraw.sol +6 -2
  20. package/commands/admin/Annotate.sol +36 -0
  21. package/commands/admin/Appoint.sol +4 -3
  22. package/commands/admin/Base.sol +8 -1
  23. package/commands/admin/Dismiss.sol +4 -3
  24. package/commands/admin/Execute.sol +2 -1
  25. package/core/Access.sol +91 -49
  26. package/core/Calls.sol +27 -22
  27. package/core/Endpoint.sol +34 -46
  28. package/core/Host.sol +63 -21
  29. package/docs/Schema.md +16 -5
  30. package/events/Annotation.sol +24 -0
  31. package/events/Guardian.sol +2 -2
  32. package/events/Introduction.sol +3 -2
  33. package/execution/Execution.sol +28 -7
  34. package/guards/Base.sol +3 -3
  35. package/package.json +1 -1
  36. package/ports/Base.sol +18 -6
  37. package/ports/Settle.sol +6 -2
  38. package/queries/Base.sol +16 -1
  39. package/utils/Accounts.sol +0 -23
  40. package/utils/Cursors.sol +65 -6
  41. package/utils/Layout.sol +0 -2
  42. package/commands/admin/Label.sol +0 -36
  43. package/commands/admin/Schemas.sol +0 -36
  44. package/events/Labeled.sol +0 -21
  45. package/events/Schema.sol +0 -23
package/CHANGELOG.md CHANGED
@@ -3,6 +3,48 @@
3
3
  Until the protocol reaches integration-stable status, minor versions may include
4
4
  breaking API changes. Breaking changes are called out explicitly.
5
5
 
6
+ ## 1.15.0
7
+
8
+ ### Breaking Changes
9
+
10
+ - Replaced the monolithic `AccessControl` base with the composable
11
+ `CommanderAccess`, `AdminAccess`, `NodeAccess`, `GuardianAccess`,
12
+ `CallerAccess`, and `TrustAccess` capabilities. `CommandBase` now requires a
13
+ concrete caller policy and no longer inherits outbound `NodeCalls`.
14
+ - Split host composition into the commander-only `CommandHost`, the advanced
15
+ `Host`, and the optional `Admins` and `Guardians` feature bundles. Commands
16
+ that use trusted outbound calls must now inherit `NodeCalls` directly and
17
+ compose a `TrustAccess` implementation.
18
+ - Removed the guardian account subtype. Guardians are now ordinary user
19
+ accounts assigned a host-local role, so previously encoded guardian account
20
+ IDs are not compatible.
21
+ - Replaced the `Labeled` and `Schema` discovery events and their dedicated admin
22
+ commands with typed blocks in the generic `Annotation` event and the
23
+ `annotate` admin command.
24
+ - Added the origin user account to `Introduction`, changing its event signature
25
+ to `Introduction(uint indexed host, uint peer, bytes32 origin, uint blocknum)`.
26
+
27
+ ### Added
28
+
29
+ - Added opt-in `Label`, `Schema`, and `Action` annotation mixins together with
30
+ canonical `#label`, `#schema`, `#annotation`, and `#action` codec support.
31
+ - Added semantic action annotations to deposit, payable deposit, withdrawal,
32
+ burn, payout, and port settlement endpoints.
33
+
34
+ ### Changed
35
+
36
+ - Enabled the Solidity optimizer with 200 runs and pinned release testing to
37
+ the Cancun EVM target, the minimum target supporting the codec's `MCOPY` use.
38
+ - Guardians can revoke node access but remain unable to grant it; admin
39
+ commands continue to require both the immutable commander caller and its
40
+ derived admin account.
41
+
42
+ ### Upgrade Compatibility
43
+
44
+ - Existing deployments are not upgradeable and this release does not preserve
45
+ storage layout or guardian mapping keys for proxy upgrades. Deploy fresh host
46
+ contracts when adopting this version.
47
+
6
48
  ## 1.14.0
7
49
 
8
50
  ### Breaking Changes
package/Core.sol CHANGED
@@ -4,12 +4,15 @@ pragma solidity ^0.8.33;
4
4
  // Aggregator: re-exports the core host, runtime, access, ledger, settlement, pipeline, node-call, and validation layer.
5
5
  // Import this file to bring the full rootzero host base layer into scope.
6
6
 
7
- import { AccessControl } from "./core/Access.sol";
7
+ import { Action } from "./annotations/Action.sol";
8
+ import { Label } from "./annotations/Label.sol";
9
+ import { Schema } from "./annotations/Schema.sol";
10
+ import { AdminAccess, CallerAccess, CommanderAccess, GuardianAccess, NodeAccess, TrustAccess } from "./core/Access.sol";
8
11
  import { Balances, InsufficientFunds } from "./core/Balances.sol";
9
12
  import { Escrows, InsufficientEscrow } from "./core/Escrows.sol";
10
13
  import { NativeAsset, Runtime } from "./core/Runtime.sol";
11
- import { Host, IHostIntroduction } from "./core/Host.sol";
12
- import { CommandCalls, FailedCall, NodeCalls, PortCalls } from "./core/Calls.sol";
14
+ import { Admins, CommandHost, Guardians, Host, HostIntroduction, IHostIntroduction } from "./core/Host.sol";
15
+ import { CommandCalls, FailedCall, NodeCalls, PortCalls, RawNodeCalls } from "./core/Calls.sol";
13
16
  import { EndpointBase } from "./core/Endpoint.sol";
14
17
  import { Pipeline } from "./core/Pipeline.sol";
15
18
  import { Budget, Budgets } from "./execution/Budget.sol";
package/Endpoints.sol CHANGED
@@ -23,13 +23,12 @@ import { Withdraw, WithdrawHook } from "./commands/Withdraw.sol";
23
23
  import { AdminBase } from "./commands/admin/Base.sol";
24
24
  import { AllowAssets, AllowAssetsHook } from "./commands/admin/AllowAssets.sol";
25
25
  import { Allowance, AllowanceHook } from "./commands/admin/Allowance.sol";
26
+ import { Annotate } from "./commands/admin/Annotate.sol";
26
27
  import { Appoint } from "./commands/admin/Appoint.sol";
27
28
  import { Authorize } from "./commands/admin/Authorize.sol";
28
29
  import { DenyAssets, DenyAssetsHook } from "./commands/admin/DenyAssets.sol";
29
30
  import { Dismiss } from "./commands/admin/Dismiss.sol";
30
31
  import { ExecutePayable } from "./commands/admin/Execute.sol";
31
- import { Label } from "./commands/admin/Label.sol";
32
- import { PublishSchema } from "./commands/admin/Schemas.sol";
33
32
  import { Unauthorize } from "./commands/admin/Unauthorize.sol";
34
33
 
35
34
  // Port endpoints
package/Events.sol CHANGED
@@ -4,6 +4,7 @@ pragma solidity ^0.8.33;
4
4
  // Aggregator: re-exports all event contracts.
5
5
  // Import this file to get access to every event emitter in one import.
6
6
 
7
+ import { AnnotationEvent } from "./events/Annotation.sol";
7
8
  import { AssetEvent, AssetStatusEvent } from "./events/Asset.sol";
8
9
  import { Actions } from "./utils/Actions.sol";
9
10
  import { BalanceEvent } from "./events/Balance.sol";
@@ -16,12 +17,10 @@ import { RecoveredEvent } from "./events/Recovered.sol";
16
17
  import { EventEmitter } from "./events/Emitter.sol";
17
18
  import { GuardianEvent } from "./events/Guardian.sol";
18
19
  import { IntroductionEvent } from "./events/Introduction.sol";
19
- import { LabeledEvent } from "./events/Labeled.sol";
20
20
  import { LockedEvent } from "./events/Locked.sol";
21
21
  import { NodeEvent } from "./events/Node.sol";
22
22
  import { RootedEvent } from "./events/Rooted.sol";
23
23
  import { RouteEvent } from "./events/Route.sol";
24
- import { SchemaEvent } from "./events/Schema.sol";
25
24
  import { SpentEvent } from "./events/Spent.sol";
26
25
  import { UndeliveredEvent } from "./events/Undelivered.sol";
27
26
  import { UnlockedEvent } from "./events/Unlocked.sol";
package/README.md CHANGED
@@ -27,18 +27,18 @@ npx create-rootzero@latest my-app
27
27
  npm install @rootzero/contracts
28
28
  ```
29
29
 
30
- A minimal host composes the base `Host` with the endpoints it needs and
31
- implements their policy hooks:
30
+ A minimal commander-only host composes `CommandHost` with the endpoints it
31
+ needs and implements their policy hooks:
32
32
 
33
33
  ```solidity
34
34
  // SPDX-License-Identifier: GPL-3.0-only
35
35
  pragma solidity ^0.8.33;
36
36
 
37
- import { Host, Balances } from "@rootzero/contracts/Core.sol";
37
+ import { CommandHost, Balances } from "@rootzero/contracts/Core.sol";
38
38
  import { Deposit } from "@rootzero/contracts/Endpoints.sol";
39
39
 
40
- contract ExampleHost is Host, Balances, Deposit {
41
- constructor(address rootzero) Host(rootzero) {}
40
+ contract ExampleHost is CommandHost, Balances, Deposit {
41
+ constructor(address commander) CommandHost(commander) {}
42
42
 
43
43
  function deposit(bytes32 account, bytes32 asset, uint amount) internal override {
44
44
  uint balance = creditTo(account, asset, amount);
@@ -47,6 +47,29 @@ contract ExampleHost is Host, Balances, Deposit {
47
47
  }
48
48
  ```
49
49
 
50
+ `CommandHost` requires a nonzero commander and accepts command calls only from
51
+ that address. It has no built-in admin commands, peer registry, guardians,
52
+ inbound introduction endpoint, generic execution command, or native-token
53
+ receive function. Use `Host` instead when the application needs those advanced
54
+ facilities; its commands accept the commander, the host itself, and explicitly
55
+ authorized host callers.
56
+
57
+ Both host types introduce themselves during deployment when the commander is a
58
+ contract. That commander must implement `introduce(uint,uint)` and accept the
59
+ call, otherwise deployment reverts. EOA commanders do not receive an
60
+ introduction call.
61
+
62
+ Host contracts are designed for fresh deployment rather than proxy upgrades.
63
+ Releases may change inheritance storage layout, immutable configuration, and
64
+ encoded identity formats; storage compatibility across versions is not
65
+ supported.
66
+
67
+ Commands using trusted outbound `NodeCalls` also require a `TrustAccess`
68
+ implementation. The advanced `Host` supplies one through its composed node access, while
69
+ `CommandHost` deliberately does not. A minimal host can explicitly compose a
70
+ custom trust policy, or a command that intentionally targets arbitrary nodes
71
+ can inherit `RawNodeCalls` instead.
72
+
50
73
  Deploy it with your own address as commander and you can call its commands
51
74
  directly. A input is a run of binary blocks — here, a single `#amount` block
52
75
  asking to deposit an asset (the encoders are a few lines each; see
@@ -164,7 +187,8 @@ Structured EVM IDs use:
164
187
  where `type` packs `[uint16 representation][uint8 category][uint8 subtype]`. A
165
188
  structured ID announces what it is (an account, an asset, a node) and which
166
189
  chain it lives on, and the payload usually embeds the underlying address. User
167
- accounts are chain-agnostic; admin and guardian accounts are chain-local.
190
+ accounts are chain-agnostic, while admin accounts are chain-local. Guardians
191
+ are normal user accounts assigned a host-specific role.
168
192
  Assets are unique IDs in the same single-word form as accounts and nodes.
169
193
  Nodes are hosts, commands, ports, queries, and guards.
170
194
 
@@ -186,13 +210,18 @@ bytes32 opaque = Ids.toKeccak(preimage); // 0x00-prefixed opaque ID
186
210
 
187
211
  A host is one contract assembled from mixins. The base `Host` brings access
188
212
  control and the admin surface (authorize, unauthorize, appoint, dismiss,
189
- label, executePayable) plus the guardian `revoke` action; you add the
213
+ annotate, executePayable) plus the guardian `revoke` action; you add the
190
214
  endpoints you need and the policy hooks they require. Keeping a ledger is
191
215
  optional: the `Balances` mixin provides one, but a host can just as well
192
216
  implement commands that hold no persistent state in the host at all —
193
217
  forwarding funds elsewhere, or operating only on the state threaded through a
194
218
  pipeline.
195
219
 
220
+ The built-in surface is also available as two independent feature bundles:
221
+ `Admins` provides annotate, authorize, unauthorize, and executePayable;
222
+ `Guardians` provides appoint, dismiss, and revoke. The full `Host` composes
223
+ both, while smaller hosts can inherit either bundle separately.
224
+
196
225
  Trust is explicit and minimal. Each host has an immutable **commander**
197
226
  address fixed at construction, from which its **admin account** is derived.
198
227
  Other contracts become callers only when their node ID is authorized into the
@@ -0,0 +1,17 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import {AnnotationEvent} from "../events/Annotation.sol";
5
+ import {Blocks} from "../codec/Blocks.sol";
6
+
7
+ /// @title Action
8
+ /// @notice Emits a primary semantic action annotation for an entity.
9
+ /// @dev For a trusted emitter, the latest action replaces the earlier value.
10
+ abstract contract Action is AnnotationEvent {
11
+ /// @notice Attach a primary semantic action to `entity`.
12
+ /// @param entity Entity receiving the action annotation.
13
+ /// @param value Canonical action identifier, such as a value from `Actions`.
14
+ function action(uint entity, uint value) internal virtual {
15
+ emit Annotation(entity, Blocks.action(value));
16
+ }
17
+ }
@@ -0,0 +1,23 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import {AnnotationEvent} from "../events/Annotation.sol";
5
+ import {Blocks} from "../codec/Blocks.sol";
6
+
7
+ /// @title Label
8
+ /// @notice Emits standard label annotation blocks for entities.
9
+ /// @dev A label is identified by its entity and namespace. For a trusted
10
+ /// emitter, the latest label in a namespace replaces the earlier value.
11
+ abstract contract Label is AnnotationEvent {
12
+ /// @notice Attach a human-readable namespaced label to `entity`.
13
+ /// @param entity Entity receiving the label annotation.
14
+ /// @param namespace Label namespace.
15
+ /// @param name Human-readable name within the namespace.
16
+ function label(
17
+ uint entity,
18
+ bytes32 namespace,
19
+ string memory name
20
+ ) internal virtual {
21
+ emit Annotation(entity, Blocks.label(namespace, name));
22
+ }
23
+ }
@@ -0,0 +1,53 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import {Blocks} from "../codec/Blocks.sol";
5
+ import {Specs} from "../codec/Specs.sol";
6
+ import {AnnotationEvent} from "../events/Annotation.sol";
7
+ import {Runtime} from "../core/Runtime.sol";
8
+
9
+ /// @title Schema
10
+ /// @notice Emits standard block-schema annotations for the current host.
11
+ /// @dev Schema annotations accumulate for distinct block keys. For a trusted
12
+ /// emitter, the latest schema for the same block key replaces the earlier claim.
13
+ abstract contract Schema is Runtime, AnnotationEvent {
14
+ /// @notice Construct and publish a context-local block specification.
15
+ /// @param key Context-local key value.
16
+ /// @param min Minimum accepted payload length.
17
+ /// @param max Maximum accepted payload length; zero means unbounded.
18
+ /// @param hint Initial per-block payload capacity.
19
+ /// @param body Schema DSL string describing the block payload body.
20
+ /// @param name Schema alias name, or zero for unnamed schemas.
21
+ /// @return spec The context-local block specification.
22
+ function schema(
23
+ uint32 key,
24
+ uint32 min,
25
+ uint32 max,
26
+ uint32 hint,
27
+ string memory body,
28
+ bytes32 name
29
+ ) internal returns (uint spec) {
30
+ spec = Specs.create(key, min, max, hint);
31
+ return schema(spec, body, name);
32
+ }
33
+
34
+ /// @notice Construct and publish an exact-size context-local block specification.
35
+ /// @param key Context-local key value.
36
+ /// @param size Exact payload length and initial per-block payload capacity.
37
+ /// @param body Schema DSL string describing the block payload body.
38
+ /// @param name Schema alias name, or zero for unnamed schemas.
39
+ /// @return spec The context-local block specification.
40
+ function schema(uint32 key, uint32 size, string memory body, bytes32 name) internal returns (uint spec) {
41
+ return schema(Specs.create(key, size), body, name);
42
+ }
43
+
44
+ /// @notice Publish an already constructed block specification for the current host.
45
+ /// @param spec Packed block specification.
46
+ /// @param body Schema DSL string describing the block payload body.
47
+ /// @param name Schema alias name, or zero for unnamed schemas.
48
+ /// @return The published block specification.
49
+ function schema(uint spec, string memory body, bytes32 name) internal returns (uint) {
50
+ emit Annotation(host, Blocks.schema(spec, body, name));
51
+ return spec;
52
+ }
53
+ }
package/codec/Blocks.sol CHANGED
@@ -754,20 +754,18 @@ library Blocks {
754
754
  /// block size and ensure the encoded payload length fits in uint32.
755
755
  /// @param dst Destination buffer.
756
756
  /// @param i Relative write position.
757
- /// @param id Node identifier.
758
757
  /// @param namespace Label namespace.
759
758
  /// @param name Label text.
760
- function writeLabel(bytes memory dst, uint i, uint id, bytes32 namespace, string memory name) internal pure {
761
- uint len = 64 + Sizes.Header + bytes(name).length;
759
+ function writeLabel(bytes memory dst, uint i, bytes32 namespace, string memory name) internal pure {
760
+ uint len = 32 + Sizes.Header + bytes(name).length;
762
761
  uint key = uint32(Keys.Label);
763
762
  uint stringkey = uint32(Keys.String);
764
763
  assembly ("memory-safe") {
765
764
  let p := add(add(dst, 0x20), i)
766
765
  mstore(p, or(shl(224, key), shl(192, len)))
767
- mstore(add(p, 0x08), id)
768
- mstore(add(p, 0x28), namespace)
766
+ mstore(add(p, 0x08), namespace)
769
767
 
770
- let q := add(p, 0x48)
768
+ let q := add(p, 0x28)
771
769
  let namelen := mload(name)
772
770
  mstore(q, or(shl(224, stringkey), shl(192, namelen)))
773
771
  mcopy(add(q, 0x08), add(name, 0x20), namelen)
@@ -1345,6 +1343,23 @@ library Blocks {
1345
1343
 
1346
1344
  // One fixed word
1347
1345
 
1346
+ /// @notice Decode one ANNOTATION block and its nested block stream.
1347
+ /// @param abs Absolute block position.
1348
+ /// @return entity Decoded entity identifier.
1349
+ /// @return stream Decoded annotation block stream.
1350
+ /// @return end Absolute position after the block.
1351
+ function unpackAnnotation(
1352
+ uint abs
1353
+ ) internal pure returns (uint entity, bytes calldata stream, uint end) {
1354
+ uint limit;
1355
+ (abs, limit) = expect(abs, Specs.Annotation);
1356
+ assembly ("memory-safe") {
1357
+ entity := calldataload(abs)
1358
+ }
1359
+ (stream, end) = unpackBytes(abs + 32);
1360
+ if (end != limit) revert InvalidBlock();
1361
+ }
1362
+
1348
1363
  /// @notice Decode one CONTEXT block and all nested byte blocks.
1349
1364
  /// @param abs Absolute block position.
1350
1365
  /// @return account Decoded account identifier.
@@ -1444,19 +1459,17 @@ library Blocks {
1444
1459
 
1445
1460
  /// @notice Decode one LABEL block and its nested name.
1446
1461
  /// @param abs Absolute block position.
1447
- /// @return id Decoded node identifier.
1448
1462
  /// @return namespace Decoded label namespace.
1449
1463
  /// @return name Decoded label text.
1450
1464
  /// @return end Absolute position after the block.
1451
- function unpackLabel(uint abs) internal pure returns (uint id, bytes32 namespace, string memory name, uint end) {
1465
+ function unpackLabel(uint abs) internal pure returns (bytes32 namespace, string memory name, uint end) {
1452
1466
  uint limit;
1453
1467
  (abs, limit) = expect(abs, Specs.Label);
1454
1468
  assembly ("memory-safe") {
1455
- id := calldataload(abs)
1456
- namespace := calldataload(add(abs, 0x20))
1469
+ namespace := calldataload(abs)
1457
1470
  }
1458
1471
  bytes calldata value;
1459
- (value, end) = unpackString(abs + 64);
1472
+ (value, end) = unpackString(abs + 32);
1460
1473
  if (end != limit) revert InvalidBlock();
1461
1474
  name = string(value);
1462
1475
  }
@@ -1532,6 +1545,30 @@ library Blocks {
1532
1545
  return create(Keys.String, bytes(value));
1533
1546
  }
1534
1547
 
1548
+ /// @notice Encode a LABEL block.
1549
+ /// @param namespace Label namespace.
1550
+ /// @param name Label text.
1551
+ /// @return Encoded LABEL block bytes.
1552
+ function label(bytes32 namespace, string memory name) internal pure returns (bytes memory) {
1553
+ return create(Keys.Label, bytes.concat(namespace, text(name)));
1554
+ }
1555
+
1556
+ /// @notice Encode an ACTION annotation block.
1557
+ /// @param value Canonical semantic action identifier.
1558
+ /// @return Encoded ACTION block bytes.
1559
+ function action(uint value) internal pure returns (bytes memory) {
1560
+ return create(Keys.Action, bytes.concat(bytes32(value)));
1561
+ }
1562
+
1563
+ /// @notice Encode a SCHEMA block.
1564
+ /// @param spec Block specification.
1565
+ /// @param body Schema body.
1566
+ /// @param name Schema name.
1567
+ /// @return Encoded SCHEMA block bytes.
1568
+ function schema(uint spec, string memory body, bytes32 name) internal pure returns (bytes memory) {
1569
+ return create(Keys.Schema, bytes.concat(bytes32(spec), text(body), name));
1570
+ }
1571
+
1535
1572
  /// @notice Encode a BALANCE block.
1536
1573
  /// @param asset Asset identifier.
1537
1574
  /// @param amount Token amount.
@@ -365,6 +365,19 @@ library Decoders {
365
365
  cur.state = cur.state.seekAbs(end);
366
366
  }
367
367
 
368
+ /// @notice Decode and consume one ANNOTATION block.
369
+ /// @param cur Cursor advanced past the block.
370
+ /// @return entity Decoded entity identifier.
371
+ /// @return data Decoded annotation block stream.
372
+ function unpackAnnotation(
373
+ Cur memory cur
374
+ ) internal pure returns (uint entity, bytes calldata data) {
375
+ uint abs = cur.state.absolute();
376
+ uint end;
377
+ (entity, data, end) = Blocks.unpackAnnotation(abs);
378
+ cur.state = cur.state.seekAbs(end);
379
+ }
380
+
368
381
  /// @notice Decode and consume one CONTEXT block.
369
382
  /// @param cur Cursor advanced past the block.
370
383
  /// @return account Decoded account identifier.
@@ -395,13 +408,12 @@ library Decoders {
395
408
 
396
409
  /// @notice Decode and consume one LABEL block.
397
410
  /// @param cur Cursor advanced past the block.
398
- /// @return id Decoded node identifier.
399
411
  /// @return namespace Decoded label namespace.
400
412
  /// @return name Decoded label text.
401
- function unpackLabel(Cur memory cur) internal pure returns (uint id, bytes32 namespace, string memory name) {
413
+ function unpackLabel(Cur memory cur) internal pure returns (bytes32 namespace, string memory name) {
402
414
  uint abs = cur.state.absolute();
403
415
  uint end;
404
- (id, namespace, name, end) = Blocks.unpackLabel(abs);
416
+ (namespace, name, end) = Blocks.unpackLabel(abs);
405
417
  cur.state = cur.state.seekAbs(end);
406
418
  }
407
419
 
package/codec/Keys.sol CHANGED
@@ -5,7 +5,7 @@ pragma solidity ^0.8.33;
5
5
  /// @notice Standard block type selectors for the rootzero block stream protocol.
6
6
  /// Standard keys use the first 4 bytes of `keccak256("#name")` by convention.
7
7
  /// Custom block keys only need to be unique in the context where they are used;
8
- /// hosts may publish custom key meanings with the `Schema` event.
8
+ /// hosts may publish custom key meanings with `#schema` annotations.
9
9
  library Keys {
10
10
  /// @dev Empty / unset key.
11
11
  bytes4 constant Empty = bytes4(0);
@@ -49,8 +49,12 @@ library Keys {
49
49
  bytes4 constant Asset = bytes4(keccak256("#asset"));
50
50
  /// @dev Node identifier - (uint id)
51
51
  bytes4 constant Node = bytes4(keccak256("#node"));
52
- /// @dev Mutable node label - (uint id, bytes32 namespace, #string as name)
52
+ /// @dev Entity label annotation - (bytes32 namespace, #string as name)
53
53
  bytes4 constant Label = bytes4(keccak256("#label"));
54
+ /// @dev Entity annotations - (uint entity, #bytes as data)
55
+ bytes4 constant Annotation = bytes4(keccak256("#annotation"));
56
+ /// @dev Primary semantic action annotation - (uint action)
57
+ bytes4 constant Action = bytes4(keccak256("#action"));
54
58
  /// @dev Block schema publication - (bytes4 key, #string as body, bytes32 name)
55
59
  bytes4 constant Schema = bytes4(keccak256("#schema"));
56
60
 
package/codec/Schema.sol CHANGED
@@ -36,7 +36,7 @@ pragma solidity ^0.8.33;
36
36
  // - generic lists use the stable key derived from `#list`
37
37
  // - standard keys are derived from block aliases, e.g. bytes4(keccak256("#amount"))
38
38
  // - custom keys are opaque bytes4 tags and only need to be unique in their
39
- // active context; use `Schema(host, spec, schema, name)` to publish their meaning
39
+ // active context; use a `#schema` annotation to publish their meaning
40
40
  // - see `docs/Schema.md` for the full working spec
41
41
  //
42
42
  // Pipeline state:
@@ -101,7 +101,12 @@ library Schemas {
101
101
  string constant Dispatch = "{ uint portal, uint resources, #bytes as payload }";
102
102
  string constant Context = "{ bytes32 account, #bytes as state, #bytes as input }";
103
103
  string constant Recover = "{ uint handler, uint resources, bytes32 key, #bytes as witness }";
104
- string constant Label = "{ uint id, bytes32 namespace, #string as name }";
104
+ string constant Annotation = "{ uint entity, #bytes as data }";
105
+
106
+ // Annotation payloads
107
+
108
+ string constant Action = "{ uint action }";
109
+ string constant Label = "{ bytes32 namespace, #string as name }";
105
110
  string constant Schema = "{ uint spec, #string as body, bytes32 name }";
106
111
  }
107
112
 
package/codec/Specs.sol CHANGED
@@ -54,6 +54,7 @@ library Specs {
54
54
  uint private constant Exact96 = 96 * SizeFields;
55
55
  uint private constant Exact128 = 128 * SizeFields;
56
56
  uint private constant UnboundedHint128 = uint(128) << 136;
57
+ uint private constant UnboundedMin40Hint256 = (uint(40) << 192) | (uint(256) << 136);
57
58
  uint private constant UnboundedMin72Hint256 = (uint(72) << 192) | (uint(256) << 136);
58
59
  uint private constant UnboundedMin48Hint512 = (uint(48) << 192) | (uint(512) << 136);
59
60
  uint private constant UnboundedMin104Hint256 = (uint(104) << 192) | (uint(256) << 136);
@@ -79,7 +80,9 @@ library Specs {
79
80
  uint constant Call = uint(bytes32(Keys.Call)) | UnboundedMin72Hint256;
80
81
  uint constant Asset = uint(bytes32(Keys.Asset)) | Exact32;
81
82
  uint constant Node = uint(bytes32(Keys.Node)) | Exact32;
82
- uint constant Label = uint(bytes32(Keys.Label)) | UnboundedMin72Hint256;
83
+ uint constant Label = uint(bytes32(Keys.Label)) | UnboundedMin40Hint256;
84
+ uint constant Annotation = uint(bytes32(Keys.Annotation)) | UnboundedMin40Hint256;
85
+ uint constant Action = uint(bytes32(Keys.Action)) | Exact32;
83
86
  uint constant Schema = uint(bytes32(Keys.Schema)) | UnboundedMin72Hint256;
84
87
 
85
88
  uint constant Status = uint(bytes32(Keys.Status)) | Exact32;
@@ -96,12 +99,31 @@ library Specs {
96
99
  /// @param hint Initial per-block payload capacity.
97
100
  /// @return spec Packed block specification.
98
101
  function create(bytes4 blockkey, uint32 min, uint32 max, uint32 hint) internal pure returns (uint spec) {
99
- spec |= uint(uint32(blockkey)) << 224;
102
+ return create(uint32(blockkey), min, max, hint);
103
+ }
104
+
105
+ /// @notice Construct a block specification from its numeric key and encoded fields.
106
+ /// @param blockkey Numeric block key.
107
+ /// @param min Minimum accepted payload length.
108
+ /// @param max Maximum accepted payload length; zero means unbounded.
109
+ /// @param hint Initial per-block payload capacity.
110
+ /// @return spec Packed block specification.
111
+ function create(uint32 blockkey, uint32 min, uint32 max, uint32 hint) internal pure returns (uint spec) {
112
+ spec |= uint(blockkey) << 224;
100
113
  spec |= uint(min) << 192;
101
114
  spec |= uint(max) << 160;
102
115
  spec |= uint(max24(hint)) << 136;
103
116
  }
104
117
 
118
+ /// @notice Construct an exact-size block specification from a numeric key.
119
+ /// @dev Sets the minimum, maximum, and allocation hint to `size`.
120
+ /// @param blockkey Numeric block key.
121
+ /// @param size Exact payload length and initial per-block payload capacity.
122
+ /// @return spec Packed exact-size block specification.
123
+ function create(uint32 blockkey, uint32 size) internal pure returns (uint spec) {
124
+ return create(blockkey, size, size, size);
125
+ }
126
+
105
127
  /// @notice Decode the block key and accepted payload range from `spec`.
106
128
  /// @param spec Packed block specification.
107
129
  /// @return blockkey Encoded block key.
package/codec/Writers.sol CHANGED
@@ -448,18 +448,16 @@ library Writers {
448
448
 
449
449
  /// @notice Append a LABEL block.
450
450
  /// @param writer Destination writer.
451
- /// @param id Node identifier to encode.
452
451
  /// @param namespace Label namespace to encode.
453
452
  /// @param name Label text to encode.
454
453
  function appendLabel(
455
454
  Writer memory writer,
456
- uint id,
457
455
  bytes32 namespace,
458
456
  string memory name
459
457
  ) internal pure {
460
- uint size = Sizes.B64 + Sizes.Header + bytes(name).length;
458
+ uint size = Sizes.B32 + Sizes.Header + bytes(name).length;
461
459
  uint i = reserve(writer, size);
462
- Blocks.writeLabel(writer.dst, i, id, namespace, name);
460
+ Blocks.writeLabel(writer.dst, i, namespace, name);
463
461
  }
464
462
 
465
463
  /// @notice Append a SCHEMA block.
package/commands/Base.sol CHANGED
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import {NodeCalls} from "../core/Calls.sol";
4
+ import {CallerAccess} from "../core/Access.sol";
5
5
  import {EndpointBase} from "../core/Endpoint.sol";
6
6
  import {Blocks} from "../codec/Blocks.sol";
7
7
  import {Specs} from "../codec/Specs.sol";
@@ -19,17 +19,10 @@ using Executions for Execution;
19
19
  /// @title CommandBase
20
20
  /// @notice Abstract base for all rootzero command contracts.
21
21
  /// Provides access control modifiers and command endpoint metadata helpers.
22
- abstract contract CommandBase is NodeCalls, EndpointBase, ReceivedEvent {
22
+ abstract contract CommandBase is CallerAccess, EndpointBase, ReceivedEvent {
23
23
  /// @dev Thrown when `onlyActive` finds that `deadline` has already passed.
24
24
  error Expired();
25
25
 
26
- /// @dev Restrict execution to the commander using the host's admin account.
27
- modifier onlyAdmin(bytes32 account) {
28
- if (account != admin) revert AccessDenied();
29
- enforceCommander(msg.sender);
30
- _;
31
- }
32
-
33
26
  /// @dev Restrict execution to trusted callers.
34
27
  modifier onlyCommand() {
35
28
  enforceCaller(msg.sender);
@@ -43,26 +36,6 @@ abstract contract CommandBase is NodeCalls, EndpointBase, ReceivedEvent {
43
36
  _;
44
37
  }
45
38
 
46
- /// @notice Close a command execution and refund unspent value to `account`.
47
- /// @param exec Command execution to close.
48
- /// @param account Account that should receive any unspent value.
49
- /// @return output Final encoded output block stream.
50
- /// @return transactions Final encoded transaction block stream.
51
- function close(
52
- Execution memory exec,
53
- bytes32 account
54
- ) internal returns (bytes memory output, bytes memory transactions) {
55
- if (exec.budget == 0 && Cursors.initial(exec.writers)) return ("", "");
56
-
57
- output = close(exec);
58
- uint amount = exec.refundValue(account, nativeAsset);
59
- if (amount != 0) {
60
- emit Received(account, nativeAsset, amount, Actions.Refund, 0);
61
- }
62
-
63
- transactions = exec.finishTransactions();
64
- }
65
-
66
39
  /// @notice Publish command metadata and a default label.
67
40
  /// @param name Command entrypoint name and default label. It must exactly
68
41
  /// match the Solidity command function name used by the canonical ABI.
@@ -83,11 +56,22 @@ abstract contract CommandBase is NodeCalls, EndpointBase, ReceivedEvent {
83
56
  bool funded,
84
57
  bool admin
85
58
  ) internal returns (uint id, uint descriptor) {
86
- id = Nodes.toCommand(Selectors.command(name), address(this));
87
- uint8 flags;
59
+ uint8 flags = 0;
88
60
  if (funded) flags |= Descriptors.Funded;
89
61
  if (admin) flags |= Descriptors.Admin;
90
- descriptor = endpoint(id, name, state, input, output, transactions, flags);
62
+ descriptor = Descriptors.create(state, input, output, transactions, flags);
63
+ return command(name, descriptor);
64
+ }
65
+
66
+ /// @notice Publish an already constructed command descriptor and default label.
67
+ /// @param name Command entrypoint name and default label. It must exactly
68
+ /// match the Solidity command function name used by the canonical ABI.
69
+ /// @param descriptor Packed command endpoint descriptor.
70
+ /// @return id Command node ID.
71
+ /// @return published Published endpoint descriptor.
72
+ function command(string memory name, uint descriptor) internal returns (uint id, uint published) {
73
+ id = Nodes.toCommand(Selectors.command(name), address(this));
74
+ published = endpoint(id, name, descriptor);
91
75
  }
92
76
 
93
77
  /// @notice Open a command state stream and return the expected output block count.
@@ -117,4 +101,24 @@ abstract contract CommandBase is NodeCalls, EndpointBase, ReceivedEvent {
117
101
  ) internal view returns (Execution memory exec) {
118
102
  return Executions.open(state, input, descriptor, batches);
119
103
  }
104
+
105
+ /// @notice Close a command execution and refund unspent value to `account`.
106
+ /// @param exec Command execution to close.
107
+ /// @param account Account that should receive any unspent value.
108
+ /// @return output Final encoded output block stream.
109
+ /// @return transactions Final encoded transaction block stream.
110
+ function close(
111
+ Execution memory exec,
112
+ bytes32 account
113
+ ) internal returns (bytes memory output, bytes memory transactions) {
114
+ if (exec.budget == 0 && Cursors.initial(exec.writers)) return ("", "");
115
+
116
+ output = close(exec);
117
+ uint amount = exec.refundValue(account, nativeAsset);
118
+ if (amount != 0) {
119
+ emit Received(account, nativeAsset, amount, Actions.Refund, 0);
120
+ }
121
+
122
+ transactions = exec.finishTransactions();
123
+ }
120
124
  }