@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
@@ -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
  }
@@ -3,7 +3,7 @@ pragma solidity ^0.8.33;
3
3
 
4
4
  import {PortBase} from "./Base.sol";
5
5
  import {AllowanceHook} from "../commands/admin/Allowance.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
 
@@ -12,26 +12,23 @@ using Cursors for Cur;
12
12
  /// Each AMOUNT block in the request is scoped to the peer host and passed to the
13
13
  /// shared allowance hook as a host-scoped allowance. Restricted to trusted peers.
14
14
  abstract contract PortAllowance is PortBase, AllowanceHook {
15
- uint internal immutable portAllowanceId = portId(this.portAllowance.selector);
15
+ bytes32 private immutable descriptor;
16
16
 
17
17
  constructor() {
18
- emit Port(host, portAllowanceId, "1:0", Schemas.Amount, "", false);
19
- emit Labeled(portAllowanceId, bytes32(0), "portAllowance");
18
+ (, descriptor) = port("portAllowance", Keys.Amount, Keys.Empty, 0, false);
20
19
  }
21
20
 
22
21
  /// @notice Execute the allowance port call.
23
22
  /// @param data AMOUNT block stream requested by the trusted peer.
24
23
  /// @return Empty response bytes.
25
24
  function portAllowance(bytes calldata data) external onlyPeer returns (bytes memory) {
26
- (Cur memory amounts, ) = Cursors.init(data, 1);
25
+ (Cur memory input, ) = openInput(data, descriptor);
27
26
  uint peer = caller();
28
27
 
29
- while (amounts.i < amounts.len) {
30
- (bytes32 asset, uint amount) = amounts.unpackAmount();
28
+ while (input.i < input.len) {
29
+ (bytes32 asset, uint amount) = input.unpackAmount();
31
30
  allowance(peer, asset, amount);
32
31
  }
33
-
34
- amounts.complete();
35
32
  return "";
36
33
  }
37
34
  }
package/ports/Base.sol CHANGED
@@ -2,15 +2,15 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import { NodeCalls } from "../core/Calls.sol";
5
- import { PortEvent } from "../events/Port.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 PortBase
10
10
  /// @notice Abstract base for peer-facing rootzero ports.
11
11
  /// Ports handle inter-host operations between cooperating hosts.
12
12
  /// Access is restricted to trusted peer callers via `onlyPeer`.
13
- abstract contract PortBase is NodeCalls, PortEvent, LabeledEvent {
13
+ abstract contract PortBase is NodeCalls, EndpointBase {
14
14
  /// @dev Thrown when the commander attempts to call a port entrypoint directly.
15
15
  error CommanderNotAllowed();
16
16
 
@@ -21,10 +21,23 @@ abstract contract PortBase is NodeCalls, PortEvent, LabeledEvent {
21
21
  _;
22
22
  }
23
23
 
24
- /// @notice Derive the deterministic node ID for a port selector on this contract.
25
- /// @param selector Port entrypoint selector.
26
- /// @return Port node ID.
27
- function portId(bytes4 selector) internal view returns (uint) {
28
- return Nodes.toPort(selector, address(this));
24
+ /// @notice Publish port metadata and a default label.
25
+ /// @param name Default human-readable port label and selector name.
26
+ /// @param input Packed input lane plus optional group byte.
27
+ /// @param output Packed output lane plus optional group byte.
28
+ /// @param selector Port ABI selector, or zero to derive it from `name`.
29
+ /// @param funded Whether the port accepts nonzero native value.
30
+ /// @return id Port node ID.
31
+ /// @return descriptor Packed endpoint lane metadata and flags.
32
+ function port(
33
+ string memory name,
34
+ bytes9 input,
35
+ bytes9 output,
36
+ bytes4 selector,
37
+ bool funded
38
+ ) internal returns (uint id, bytes32 descriptor) {
39
+ selector = selector == bytes4(0) ? Selectors.port(name) : selector;
40
+ id = Nodes.toPort(selector, address(this));
41
+ descriptor = endpoint(id, name, bytes9(0), input, output, funded, false);
29
42
  }
30
43
  }
package/ports/Credit.sol CHANGED
@@ -3,7 +3,7 @@ pragma solidity ^0.8.33;
3
3
 
4
4
  import { PortBase } from "./Base.sol";
5
5
  import { CreditAccountHook } from "../commands/Credit.sol";
6
- import { Cursors, Cur, Forms } 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 lets a trusted peer credit supplied accounts directly.
12
12
  /// Each ACCOUNT_AMOUNT block calls `creditAccount` for its account.
13
13
  abstract contract PortCreditAccount is PortBase, CreditAccountHook {
14
- uint internal immutable portCreditAccountId = portId(this.portCreditAccount.selector);
14
+ bytes32 private immutable descriptor;
15
15
 
16
16
  constructor() {
17
- emit Port(host, portCreditAccountId, "1:0", Forms.AccountAmount, "", false);
18
- emit Labeled(portCreditAccountId, bytes32(0), "portCreditAccount");
17
+ (, descriptor) = port("portCreditAccount", Keys.AccountAmount, Keys.Empty, 0, false);
19
18
  }
20
19
 
21
20
  /// @notice Execute the port-credit call.
22
21
  /// @param data ACCOUNT_AMOUNT block stream supplied by the trusted peer.
23
22
  /// @return Empty response bytes.
24
23
  function portCreditAccount(bytes calldata data) external onlyPeer returns (bytes memory) {
25
- (Cur memory amounts, ) = Cursors.init(data, 1);
24
+ (Cur memory input, ) = openInput(data, descriptor);
26
25
 
27
- while (amounts.i < amounts.len) {
28
- (bytes32 account, bytes32 asset, uint amount) = amounts.unpackAccountAmount();
26
+ while (input.i < input.len) {
27
+ (bytes32 account, bytes32 asset, uint amount) = input.unpackAccountAmount();
29
28
  creditAccount(account, asset, amount);
30
29
  }
31
-
32
- amounts.complete();
33
30
  return "";
34
31
  }
35
32
  }
package/ports/Debit.sol CHANGED
@@ -3,7 +3,7 @@ pragma solidity ^0.8.33;
3
3
 
4
4
  import { PortBase } from "./Base.sol";
5
5
  import { DebitAccountHook } from "../commands/Debit.sol";
6
- import { Cursors, Cur, Forms } 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 lets a trusted peer debit supplied accounts directly.
12
12
  /// Each ACCOUNT_AMOUNT block calls `debitAccount` for its account.
13
13
  abstract contract PortDebitAccount is PortBase, DebitAccountHook {
14
- uint internal immutable portDebitAccountId = portId(this.portDebitAccount.selector);
14
+ bytes32 private immutable descriptor;
15
15
 
16
16
  constructor() {
17
- emit Port(host, portDebitAccountId, "1:0", Forms.AccountAmount, "", false);
18
- emit Labeled(portDebitAccountId, bytes32(0), "portDebitAccount");
17
+ (, descriptor) = port("portDebitAccount", Keys.AccountAmount, Keys.Empty, 0, false);
19
18
  }
20
19
 
21
20
  /// @notice Execute the port-debit call.
22
21
  /// @param data ACCOUNT_AMOUNT block stream supplied by the trusted peer.
23
22
  /// @return Empty response bytes.
24
23
  function portDebitAccount(bytes calldata data) external onlyPeer returns (bytes memory) {
25
- (Cur memory amounts, ) = Cursors.init(data, 1);
24
+ (Cur memory input, ) = openInput(data, descriptor);
26
25
 
27
- while (amounts.i < amounts.len) {
28
- (bytes32 account, bytes32 asset, uint amount) = amounts.unpackAccountAmount();
26
+ while (input.i < input.len) {
27
+ (bytes32 account, bytes32 asset, uint amount) = input.unpackAccountAmount();
29
28
  debitAccount(account, asset, amount);
30
29
  }
31
-
32
- amounts.complete();
33
30
  return "";
34
31
  }
35
32
  }
@@ -3,7 +3,7 @@ pragma solidity ^0.8.33;
3
3
 
4
4
  import {PortBase} from "./Base.sol";
5
5
  import {DenyAssetsHook} from "../commands/admin/DenyAssets.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 blocks a list of assets on behalf of a peer host.
12
12
  /// Each ASSET block in the request calls `denyAsset`. Restricted to trusted peers.
13
13
  abstract contract PortDenyAssets is PortBase, DenyAssetsHook {
14
- uint internal immutable portDenyAssetsId = portId(this.portDenyAssets.selector);
14
+ bytes32 private immutable descriptor;
15
15
 
16
16
  constructor() {
17
- emit Port(host, portDenyAssetsId, "1:0", Schemas.Asset, "", false);
18
- emit Labeled(portDenyAssetsId, bytes32(0), "portDenyAssets");
17
+ (, descriptor) = port("portDenyAssets", Keys.Asset, Keys.Empty, 0, false);
19
18
  }
20
19
 
21
20
  /// @notice Execute the deny-assets peer call.
22
21
  /// @param data ASSET block stream supplied by the trusted peer.
23
22
  /// @return Empty response bytes.
24
23
  function portDenyAssets(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
  denyAsset(asset);
30
29
  }
31
-
32
- assets.complete();
33
30
  return "";
34
31
  }
35
32
  }
@@ -4,7 +4,7 @@ pragma solidity ^0.8.33;
4
4
  import { PortBase } from "./Base.sol";
5
5
  import { Payable } from "../core/Payable.sol";
6
6
  import { RoutePayableHook } from "../commands/Relay.sol";
7
- import { Cursors, Cur, Schemas } from "../Cursors.sol";
7
+ import { Cursors, Cur, Keys } from "../Cursors.sol";
8
8
  import { Budget } from "../utils/Value.sol";
9
9
 
10
10
  using Cursors for Cur;
@@ -12,28 +12,25 @@ using Cursors for Cur;
12
12
  /// @title PortDispatchPayable
13
13
  /// @notice Port endpoint that forwards DISPATCH blocks to a host-defined route hook.
14
14
  abstract contract PortDispatchPayable is PortBase, Payable, RoutePayableHook {
15
- uint internal immutable portDispatchPayableId = portId(this.portDispatchPayable.selector);
15
+ bytes32 private immutable descriptor;
16
16
 
17
17
  constructor() {
18
- emit Port(host, portDispatchPayableId, "1:0", Schemas.Dispatch, "", true);
19
- emit Labeled(portDispatchPayableId, bytes32(0), "portDispatchPayable");
18
+ (, descriptor) = port("portDispatchPayable", Keys.Dispatch, Keys.Empty, 0, true);
20
19
  }
21
20
 
22
21
  /// @notice Forward peer-supplied dispatches to the host-defined route hook.
23
22
  /// @dev Route hooks receive the shared top-level source value
24
23
  /// budget. Any `msg.value` not spent by the hook remains on this host.
25
24
  /// @param data DISPATCH block stream supplied by the trusted peer.
26
- /// @return output Empty response bytes.
27
- function portDispatchPayable(bytes calldata data) external payable onlyPeer returns (bytes memory output) {
28
- (Cur memory input, ) = Cursors.init(data, 1);
25
+ /// @return Empty response bytes.
26
+ function portDispatchPayable(bytes calldata data) external payable onlyPeer returns (bytes memory) {
27
+ (Cur memory input, ) = openInput(data, descriptor);
29
28
  Budget memory budget = openValue();
30
29
 
31
30
  while (input.i < input.len) {
32
31
  (uint portal, uint resources, bytes calldata payload) = input.unpackDispatch();
33
32
  route(portal, resources, bytes(payload), budget);
34
33
  }
35
-
36
- input.complete();
37
34
  return "";
38
35
  }
39
36
 
package/ports/Pipe.sol CHANGED
@@ -3,7 +3,7 @@ pragma solidity ^0.8.33;
3
3
 
4
4
  import {PortBase} from "./Base.sol";
5
5
  import {Pipeline} from "../core/Pipeline.sol";
6
- import {Cursors, Cur, Schemas} from "../Cursors.sol";
6
+ import {Cursors, Cur, Keys} from "../Cursors.sol";
7
7
  import {Budget} from "../utils/Value.sol";
8
8
 
9
9
  using Cursors for Cur;
@@ -12,11 +12,10 @@ using Cursors for Cur;
12
12
  /// @notice Port that consumes CONTEXT blocks and executes each request as a step stream.
13
13
  /// Each context's request bytes are passed to the shared pipeline.
14
14
  abstract contract PortPipePayable is PortBase, Pipeline {
15
- uint internal immutable portPipePayableId = portId(this.portPipePayable.selector);
15
+ bytes32 private immutable descriptor;
16
16
 
17
17
  constructor() {
18
- emit Port(host, portPipePayableId, "1:0", Schemas.Context, "", true);
19
- emit Labeled(portPipePayableId, bytes32(0), "portPipePayable");
18
+ (, descriptor) = port("portPipePayable", Keys.Context, Keys.Empty, 0, true);
20
19
  }
21
20
 
22
21
  /// @notice Execute peer-supplied contexts through the shared payable pipe.
@@ -25,15 +24,13 @@ abstract contract PortPipePayable is PortBase, Pipeline {
25
24
  /// @param data CONTEXT block stream supplied by the trusted peer.
26
25
  /// @return Empty response bytes.
27
26
  function portPipePayable(bytes calldata data) external payable onlyPeer returns (bytes memory) {
28
- (Cur memory input, ) = Cursors.init(data, 1);
27
+ (Cur memory input, ) = openInput(data, descriptor);
29
28
  Budget memory budget = openValue();
30
29
 
31
30
  while (input.i < input.len) {
32
31
  (bytes32 account, bytes calldata state, bytes calldata request) = input.unpackContext();
33
32
  pipe(account, state, request, budget);
34
33
  }
35
-
36
- input.complete();
37
34
  return "";
38
35
  }
39
36
  }
package/ports/Redeem.sol CHANGED
@@ -2,7 +2,7 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import {PortBase} from "./Base.sol";
5
- import {Cursors, Cur, Schemas} from "../Cursors.sol";
5
+ import {Cursors, Cur, Keys} from "../Cursors.sol";
6
6
 
7
7
  using Cursors for Cur;
8
8
 
@@ -19,26 +19,23 @@ abstract contract RedeemBalanceHook {
19
19
  /// Each BALANCE block in the request calls `redeemBalance(peer, asset, amount)`.
20
20
  /// Restricted to trusted peers.
21
21
  abstract contract PortRedeemBalance is PortBase, RedeemBalanceHook {
22
- uint internal immutable portRedeemBalanceId = portId(this.portRedeemBalance.selector);
22
+ bytes32 private immutable descriptor;
23
23
 
24
24
  constructor() {
25
- emit Port(host, portRedeemBalanceId, "1:0", Schemas.Balance, "", false);
26
- emit Labeled(portRedeemBalanceId, bytes32(0), "portRedeemBalance");
25
+ (, descriptor) = port("portRedeemBalance", Keys.Balance, Keys.Empty, 0, false);
27
26
  }
28
27
 
29
28
  /// @notice Execute the balance redemption port call.
30
29
  /// @param data BALANCE block stream supplied by the trusted peer.
31
30
  /// @return Empty response bytes.
32
31
  function portRedeemBalance(bytes calldata data) external onlyPeer returns (bytes memory) {
33
- (Cur memory input, ) = Cursors.init(data, 1);
32
+ (Cur memory input, ) = openInput(data, descriptor);
34
33
  uint peer = caller();
35
34
 
36
35
  while (input.i < input.len) {
37
36
  (bytes32 asset, uint amount) = input.unpackBalance();
38
37
  redeemBalance(peer, asset, amount);
39
38
  }
40
-
41
- input.complete();
42
39
  return "";
43
40
  }
44
41
  }
package/ports/Settle.sol CHANGED
@@ -4,7 +4,7 @@ pragma solidity ^0.8.33;
4
4
  import { PortBase } from "./Base.sol";
5
5
  import { CreditAccountHook } from "../commands/Credit.sol";
6
6
  import { DebitAccountHook } from "../commands/Debit.sol";
7
- import { Cursors, Cur, Schemas } from "../Cursors.sol";
7
+ import { Cursors, Cur, Keys } from "../Cursors.sol";
8
8
 
9
9
  using Cursors for Cur;
10
10
 
@@ -12,26 +12,23 @@ using Cursors for Cur;
12
12
  /// @notice Port that consumes peer-supplied TRANSACTION blocks through debit and credit hooks.
13
13
  /// Each TRANSACTION block calls `debitAccount` for `from` and `creditAccount` for `to`.
14
14
  abstract contract PortSettle is PortBase, DebitAccountHook, CreditAccountHook {
15
- uint internal immutable portSettleId = portId(this.portSettle.selector);
15
+ bytes32 private immutable descriptor;
16
16
 
17
17
  constructor() {
18
- emit Port(host, portSettleId, "1:0", Schemas.Transaction, "", false);
19
- emit Labeled(portSettleId, bytes32(0), "portSettle");
18
+ (, descriptor) = port("portSettle", Keys.Transaction, Keys.Empty, 0, false);
20
19
  }
21
20
 
22
21
  /// @notice Execute the port-settle call.
23
22
  /// @param data TRANSACTION block stream supplied by the trusted peer.
24
23
  /// @return Empty response bytes.
25
24
  function portSettle(bytes calldata data) external onlyPeer returns (bytes memory) {
26
- (Cur memory state, ) = Cursors.init(data, 1);
25
+ (Cur memory input, ) = openInput(data, descriptor);
27
26
 
28
- while (state.i < state.len) {
29
- (bytes32 from, bytes32 to, bytes32 asset, uint amount) = state.unpackTransaction();
27
+ while (input.i < input.len) {
28
+ (bytes32 from, bytes32 to, bytes32 asset, uint amount) = input.unpackTransaction();
30
29
  if (from != 0) debitAccount(from, asset, amount);
31
30
  if (to != 0) creditAccount(to, asset, amount);
32
31
  }
33
-
34
- state.complete();
35
32
  return "";
36
33
  }
37
34
  }
@@ -1,8 +1,7 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import {Cur, Cursors, Writer, Writers} from "../Cursors.sol";
5
- import {Forms, Schemas} from "../blocks/Schema.sol";
4
+ import {Cur, Cursors, Keys, Writer, Writers} from "../Cursors.sol";
6
5
  import {QueryBase} from "./Base.sol";
7
6
 
8
7
  using Cursors for Cur;
@@ -21,27 +20,25 @@ abstract contract AssetStatusHook {
21
20
  /// The request is a run of `ASSET` blocks.
22
21
  /// The response returns one `STATUS` form block per query entry, preserving request order.
23
22
  abstract contract AssetStatus is QueryBase, AssetStatusHook {
24
- uint public immutable assetStatusId = queryId(this.assetStatus.selector);
23
+ bytes32 private immutable descriptor;
25
24
 
26
25
  constructor() {
27
- emit Query(host, assetStatusId, "1:1", Schemas.Asset, Forms.Status);
28
- emit Labeled(assetStatusId, bytes32(0), "assetStatus");
26
+ (, descriptor) = query("assetStatus", Keys.Asset, Keys.Status, 0);
29
27
  }
30
28
 
31
29
  /// @notice Resolve asset support status for a run of requested assets.
32
- /// @param request Block-stream request consisting of `#asset { bytes32 asset }` blocks.
33
- /// @return Block-stream response containing one `#status { uint code }` per asset block.
30
+ /// @param request Block-stream request consisting of `asset { bytes32 asset }` blocks.
31
+ /// @return Block-stream response containing one `status { uint code }` form block per asset block.
34
32
  function assetStatus(bytes calldata request) external view returns (bytes memory) {
35
- (Cur memory query, uint groups) = Cursors.init(request, 1);
36
- Writer memory response = Writers.allocStatuses(groups);
33
+ (Cur memory input, uint outputs) = openInput(request, descriptor);
34
+ Writer memory response = Writers.allocStatuses(outputs);
37
35
 
38
- while (query.i < query.len) {
39
- bytes32 asset = query.unpackAsset();
36
+ while (input.i < input.len) {
37
+ bytes32 asset = input.unpackAsset();
40
38
  uint status = assetStatus(asset);
41
39
  response.appendStatus(status);
42
40
  }
43
41
 
44
- query.complete();
45
42
  return response.finish();
46
43
  }
47
44
  }
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import {Cur, Cursors, Forms, Writer, Writers} from "../Cursors.sol";
4
+ import {Cur, Cursors, Keys, Writer, Writers} from "../Cursors.sol";
5
5
  import {QueryBase} from "./Base.sol";
6
6
 
7
7
  using Cursors for Cur;
@@ -21,27 +21,25 @@ abstract contract GetBalancesHook {
21
21
  /// The request is a run of `ACCOUNT_ASSET` form blocks.
22
22
  /// The response returns one `ACCOUNT_AMOUNT` form block per requested position, preserving request order.
23
23
  abstract contract GetBalances is QueryBase, GetBalancesHook {
24
- uint public immutable getBalancesId = queryId(this.getBalances.selector);
24
+ bytes32 private immutable descriptor;
25
25
 
26
26
  constructor() {
27
- emit Query(host, getBalancesId, "1:1", Forms.AccountAsset, Forms.AccountAmount);
28
- emit Labeled(getBalancesId, bytes32(0), "getBalances");
27
+ (, descriptor) = query("getBalances", Keys.AccountAsset, Keys.AccountAmount, 0);
29
28
  }
30
29
 
31
30
  /// @notice Resolve balances for a run of requested `(account, asset)` tuples.
32
31
  /// @param request Block-stream request consisting of `accountAsset(account, asset)*`.
33
32
  /// @return Block-stream response containing one `accountAmount(account, asset, amount)` block per request block.
34
33
  function getBalances(bytes calldata request) external view returns (bytes memory) {
35
- (Cur memory query, uint groups) = Cursors.init(request, 1);
36
- Writer memory response = Writers.allocAccountAmounts(groups);
34
+ (Cur memory input, uint outputs) = openInput(request, descriptor);
35
+ Writer memory response = Writers.allocAccountAmounts(outputs);
37
36
 
38
- while (query.i < query.len) {
39
- (bytes32 account, bytes32 asset) = query.unpackAccountAsset();
37
+ while (input.i < input.len) {
38
+ (bytes32 account, bytes32 asset) = input.unpackAccountAsset();
40
39
  uint amount = getBalance(account, asset);
41
40
  response.appendAccountAmount(account, asset, amount);
42
41
  }
43
42
 
44
- query.complete();
45
43
  return response.finish();
46
44
  }
47
45
  }
package/queries/Base.sol CHANGED
@@ -1,23 +1,31 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import { Runtime } from "../core/Runtime.sol";
5
- import { LabeledEvent } from "../events/Labeled.sol";
6
- import { QueryEvent } from "../events/Query.sol";
4
+ import { EndpointBase } from "../core/Endpoint.sol";
7
5
  import { Nodes } from "../utils/Nodes.sol";
6
+ import { Selectors } from "../utils/Selectors.sol";
8
7
 
9
8
  /// @title QueryBase
10
9
  /// @notice Abstract base for rootzero query contracts.
11
10
  /// Queries are view-only entry points that consume a block-stream request and
12
11
  /// return a block-stream response.
13
- abstract contract QueryBase is Runtime, QueryEvent, LabeledEvent {
12
+ abstract contract QueryBase is EndpointBase {
14
13
 
15
- /// @notice Derive the deterministic node ID for a query selector on this contract.
16
- /// The ID encodes the ABI selector and `address(this)`, making it unique
17
- /// per (function selector, contract address) pair.
18
- /// @param selector Query entrypoint selector.
19
- /// @return Query node ID.
20
- function queryId(bytes4 selector) internal view returns (uint) {
21
- return Nodes.toQuery(selector, address(this));
14
+ /// @notice Publish query metadata and a default label.
15
+ /// @param name Default human-readable query label and selector name.
16
+ /// @param input Packed input lane plus optional group byte.
17
+ /// @param output Packed output lane plus optional group byte.
18
+ /// @param selector Query ABI selector, or zero to derive it from `name`.
19
+ /// @return id Query node ID.
20
+ /// @return descriptor Packed endpoint lane metadata and flags.
21
+ function query(
22
+ string memory name,
23
+ bytes9 input,
24
+ bytes9 output,
25
+ bytes4 selector
26
+ ) internal returns (uint id, bytes32 descriptor) {
27
+ selector = selector == bytes4(0) ? Selectors.query(name) : selector;
28
+ id = Nodes.toQuery(selector, address(this));
29
+ descriptor = endpoint(id, name, bytes9(0), input, output, false, false);
22
30
  }
23
31
  }
@@ -0,0 +1,49 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ /// @title Selectors
5
+ /// @notice Canonical ABI selector derivation for rootzero endpoint types.
6
+ library Selectors {
7
+ /// @notice Derive the ABI selector for a command entrypoint.
8
+ /// @param name Command function name.
9
+ /// @return Selector for `name((bytes32,bytes,bytes))`.
10
+ function command(string memory name) internal pure returns (bytes4) {
11
+ return derive(name, "((bytes32,bytes,bytes))");
12
+ }
13
+
14
+ /// @notice Derive the ABI selector for a port entrypoint.
15
+ /// @param name Port function name.
16
+ /// @return Selector for `name(bytes)`.
17
+ function port(string memory name) internal pure returns (bytes4) {
18
+ return direct(name);
19
+ }
20
+
21
+ /// @notice Derive the ABI selector for a query entrypoint.
22
+ /// @param name Query function name.
23
+ /// @return Selector for `name(bytes)`.
24
+ function query(string memory name) internal pure returns (bytes4) {
25
+ return direct(name);
26
+ }
27
+
28
+ /// @notice Derive the ABI selector for a guard action entrypoint.
29
+ /// @param name Guard action function name.
30
+ /// @return Selector for `name(bytes)`.
31
+ function guard(string memory name) internal pure returns (bytes4) {
32
+ return direct(name);
33
+ }
34
+
35
+ /// @dev Derive a direct endpoint selector with a single `bytes` argument.
36
+ /// @param name Endpoint function name.
37
+ /// @return Selector for `name(bytes)`.
38
+ function direct(string memory name) private pure returns (bytes4) {
39
+ return derive(name, "(bytes)");
40
+ }
41
+
42
+ /// @dev Derive an ABI selector from a function name and argument signature suffix.
43
+ /// @param name Function name.
44
+ /// @param args Parenthesized ABI argument signature.
45
+ /// @return First four bytes of the function signature hash.
46
+ function derive(string memory name, string memory args) private pure returns (bytes4) {
47
+ return bytes4(keccak256(bytes(string.concat(name, args))));
48
+ }
49
+ }
@@ -1,43 +0,0 @@
1
- // SPDX-License-Identifier: GPL-3.0-only
2
- pragma solidity ^0.8.33;
3
-
4
- import { AdminBase, CommandContext, Keys } from "./Base.sol";
5
- import { Cursors, Cur } from "../../Cursors.sol";
6
-
7
- using Cursors for Cur;
8
-
9
- abstract contract DestroyHook {
10
- /// @notice Override to run host teardown or destruction logic.
11
- /// @param input Cursor over the full request byte stream.
12
- function destroy(Cur memory input) internal virtual;
13
- }
14
-
15
- /// @title Destroy
16
- /// @notice Admin command that runs host teardown logic via a virtual hook.
17
- /// The full request is passed to `destroy` as a cursor. Only callable by the admin account.
18
- abstract contract Destroy is AdminBase, DestroyHook {
19
- uint internal immutable destroyId = commandId(this.destroy.selector);
20
-
21
- /// @param input Request schema advertised for destruction input.
22
- constructor(string memory input) {
23
- emit Admin(host, destroyId, "1:0:0", input, Keys.Empty, Keys.Empty, false);
24
- emit Labeled(destroyId, bytes32(0), "destroy");
25
- }
26
-
27
- /// @notice Run host teardown logic over the full admin request.
28
- /// @param c Admin command context; `c.request` is passed through as a cursor.
29
- /// @return Empty output state.
30
- function destroy(
31
- CommandContext calldata c
32
- ) external onlyAdmin(c.account) returns (bytes memory) {
33
- destroy(Cursors.open(c.request));
34
- return "";
35
- }
36
- }
37
-
38
-
39
-
40
-
41
-
42
-
43
-