@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
@@ -2,38 +2,47 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import {CommandBase, CommandContext, Keys} from "./Base.sol";
5
+ import {Lane} from "../core/Endpoint.sol";
5
6
  import {Payable} from "../core/Payable.sol";
6
- import {RoutePayableHook} from "../core/Portal.sol";
7
- import {Cursors, Cur, Schemas} from "../Cursors.sol";
7
+ import {Cursors, Cur} from "../Cursors.sol";
8
8
  import {Budget} from "../utils/Value.sol";
9
9
 
10
10
  using Cursors for Cur;
11
11
 
12
+ abstract contract RoutePayableHook {
13
+ /// @notice Override to route an encoded payload through `portal`.
14
+ /// @param portal Destination portal identifier, often the destination host ID.
15
+ /// @param resources Chain-specific destination resources. EVM adapters
16
+ /// may interpret this as packed execution gas and destination value.
17
+ /// @param payload Encoded payload ready for the transport layer.
18
+ /// @param budget Source native-value budget available for transport
19
+ /// fees and destination resource funding.
20
+ function route(uint portal, uint resources, bytes memory payload, Budget memory budget) internal virtual;
21
+ }
22
+
12
23
  /// @title RelayPayable
13
24
  /// @notice Command that forwards one RELAY block to a host-defined relay hook.
14
25
  /// Reverts unless the request contains exactly one RELAY block, preventing
15
26
  /// the same state from being duplicated across multiple relays.
16
27
  /// Produces no output state.
17
28
  abstract contract RelayPayable is CommandBase, Payable, RoutePayableHook {
18
- uint internal immutable relayPayableId = commandId(this.relayPayable.selector);
29
+ bytes32 private immutable descriptor;
19
30
 
20
31
  constructor() {
21
- emit Command(host, relayPayableId, "1:0:0", Schemas.Relay, Keys.Any, Keys.Empty, true);
22
- emit Labeled(relayPayableId, bytes32(0), "relayPayable");
32
+ (, descriptor) = command("relayPayable", Keys.Any, Keys.Relay, Keys.Empty, 0, true, false);
23
33
  }
24
34
 
25
35
  /// @notice Relay one RELAY request block with the command account and current state.
26
- /// @param c Command context; `c.request` must contain exactly one RELAY block.
27
- /// @return output Empty output state.
28
- function relayPayable(CommandContext calldata c) external payable onlyCommand returns (bytes memory output) {
29
- (Cur memory request, ) = Cursors.init(c.request, 1, 1);
30
- Budget memory budget = openValue();
36
+ /// @param c Command context; `c.input` must contain exactly one RELAY block.
37
+ /// @return Empty output state.
38
+ function relayPayable(CommandContext calldata c) external payable onlyCommand returns (bytes memory) {
39
+ (Cur memory input, , ) = openLane(c.input, descriptor, Lane.Input, 1);
40
+ (uint portal, uint resources, bytes memory context) = input.relayToContext(c.account, c.state);
31
41
 
32
- (uint portal, uint resources, bytes memory context) = request.relayToContext(c.account, c.state);
42
+ Budget memory budget = openValue();
33
43
  route(portal, resources, context, budget);
34
44
 
35
45
  closeValue(c.account, budget);
36
- request.complete();
37
46
  return "";
38
47
  }
39
48
  }
@@ -19,11 +19,10 @@ abstract contract WithdrawHook {
19
19
  /// Use `withdraw` for assets being sent outside the protocol (e.g. ERC-20 transfers, ETH sends).
20
20
  /// For internal balance credits, use `creditAccount` instead.
21
21
  abstract contract Withdraw is CommandBase, WithdrawHook {
22
- uint internal immutable withdrawId = commandId(this.withdraw.selector);
22
+ bytes32 private immutable descriptor;
23
23
 
24
24
  constructor() {
25
- emit Command(host, withdrawId, "0:1:0", "", Keys.Balance, Keys.Empty, false);
26
- emit Labeled(withdrawId, bytes32(0), "withdraw");
25
+ (, descriptor) = command("withdraw", Keys.Balance, Keys.Empty, Keys.Empty, 0, false, false);
27
26
  }
28
27
 
29
28
  /// @notice Withdraw each BALANCE block from the command state to the command account.
@@ -32,14 +31,12 @@ abstract contract Withdraw is CommandBase, WithdrawHook {
32
31
  function withdraw(
33
32
  CommandContext calldata c
34
33
  ) external onlyCommand returns (bytes memory) {
35
- (Cur memory state, , ) = Cursors.init(c.state, 1);
34
+ (Cur memory state, ) = openState(c.state, descriptor);
36
35
 
37
36
  while (state.i < state.len) {
38
37
  (bytes32 asset, uint amount) = state.unpackBalance();
39
38
  withdraw(c.account, asset, amount);
40
39
  }
41
-
42
- state.complete();
43
40
  return "";
44
41
  }
45
42
  }
@@ -2,7 +2,7 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import { AdminBase, CommandContext, Keys } from "./Base.sol";
5
- import { Cursors, Cur, Schemas } from "../../Cursors.sol";
5
+ import { Cursors, Cur } from "../../Cursors.sol";
6
6
  using Cursors for Cur;
7
7
 
8
8
  abstract contract AllowAssetsHook {
@@ -16,27 +16,24 @@ abstract contract AllowAssetsHook {
16
16
  /// @notice Admin command that permits a list of assets via a virtual hook.
17
17
  /// Each ASSET block in the request calls `allowAsset`. Only callable by the admin account.
18
18
  abstract contract AllowAssets is AdminBase, AllowAssetsHook {
19
- uint internal immutable allowAssetsId = commandId(this.allowAssets.selector);
19
+ bytes32 private immutable descriptor;
20
20
 
21
21
  constructor() {
22
- emit Admin(host, allowAssetsId, "1:0:0", Schemas.Asset, Keys.Empty, Keys.Empty, false);
23
- emit Labeled(allowAssetsId, bytes32(0), "allowAssets");
22
+ (, descriptor) = command("allowAssets", Keys.Empty, Keys.Asset, Keys.Empty, 0, false, true);
24
23
  }
25
24
 
26
25
  /// @notice Allow each ASSET block in the admin request.
27
- /// @param c Admin command context; `c.request` must contain ASSET blocks.
26
+ /// @param c Admin command context; `c.input` must contain ASSET blocks.
28
27
  /// @return Empty output state.
29
28
  function allowAssets(
30
29
  CommandContext calldata c
31
30
  ) external onlyAdmin(c.account) returns (bytes memory) {
32
- (Cur memory request, , ) = Cursors.init(c.request, 1);
31
+ (Cur memory input, ) = openInput(c.input, descriptor);
33
32
 
34
- while (request.i < request.len) {
35
- bytes32 asset = request.unpackAsset();
33
+ while (input.i < input.len) {
34
+ bytes32 asset = input.unpackAsset();
36
35
  allowAsset(asset);
37
36
  }
38
-
39
- request.complete();
40
37
  return "";
41
38
  }
42
39
  }
@@ -2,7 +2,7 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import {AdminBase, CommandContext, Keys} from "./Base.sol";
5
- import {Cursors, Cur, Schemas} from "../../Cursors.sol";
5
+ import {Cursors, Cur} from "../../Cursors.sol";
6
6
  using Cursors for Cur;
7
7
 
8
8
  abstract contract AllowanceHook {
@@ -20,25 +20,22 @@ abstract contract AllowanceHook {
20
20
  /// @notice Admin command that applies cross-host allowance entries via a virtual hook.
21
21
  /// Each ALLOWANCE block grants or updates a host-scoped asset cap. Only callable by the admin account.
22
22
  abstract contract Allowance is AdminBase, AllowanceHook {
23
- uint internal immutable allowanceId = commandId(this.allowance.selector);
23
+ bytes32 private immutable descriptor;
24
24
 
25
25
  constructor() {
26
- emit Admin(host, allowanceId, "1:0:0", Schemas.Allowance, Keys.Empty, Keys.Empty, false);
27
- emit Labeled(allowanceId, bytes32(0), "allowance");
26
+ (, descriptor) = command("allowance", Keys.Empty, Keys.Allowance, Keys.Empty, 0, false, true);
28
27
  }
29
28
 
30
29
  /// @notice Apply each ALLOWANCE block in the admin request.
31
- /// @param c Admin command context; `c.request` must contain ALLOWANCE blocks.
30
+ /// @param c Admin command context; `c.input` must contain ALLOWANCE blocks.
32
31
  /// @return Empty output state.
33
32
  function allowance(CommandContext calldata c) external onlyAdmin(c.account) returns (bytes memory) {
34
- (Cur memory request, , ) = Cursors.init(c.request, 1);
33
+ (Cur memory input, ) = openInput(c.input, descriptor);
35
34
 
36
- while (request.i < request.len) {
37
- (uint peer, bytes32 asset, uint amount) = request.unpackAllowance();
35
+ while (input.i < input.len) {
36
+ (uint peer, bytes32 asset, uint amount) = input.unpackAllowance();
38
37
  allowance(peer, asset, amount);
39
38
  }
40
-
41
- request.complete();
42
39
  return "";
43
40
  }
44
41
  }
@@ -2,7 +2,7 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import { AdminBase, CommandContext, Keys } from "./Base.sol";
5
- import { Cursors, Cur, Schemas } from "../../Cursors.sol";
5
+ import { Cursors, Cur } from "../../Cursors.sol";
6
6
  using Cursors for Cur;
7
7
 
8
8
  /// @title Appoint
@@ -10,27 +10,24 @@ using Cursors for Cur;
10
10
  /// Each ACCOUNT block in the request is enabled as a guardian on the host.
11
11
  /// Only callable by the admin account.
12
12
  abstract contract Appoint is AdminBase {
13
- uint internal immutable appointId = commandId(this.appoint.selector);
13
+ bytes32 private immutable descriptor;
14
14
 
15
15
  constructor() {
16
- emit Admin(host, appointId, "1:0:0", Schemas.Account, Keys.Empty, Keys.Empty, false);
17
- emit Labeled(appointId, bytes32(0), "appoint");
16
+ (, descriptor) = command("appoint", Keys.Empty, Keys.Account, Keys.Empty, 0, false, true);
18
17
  }
19
18
 
20
19
  /// @notice Appoint each ACCOUNT block in the admin request as a guardian.
21
- /// @param c Admin command context; `c.request` must contain ACCOUNT blocks.
20
+ /// @param c Admin command context; `c.input` must contain ACCOUNT blocks.
22
21
  /// @return Empty output state.
23
22
  function appoint(
24
23
  CommandContext calldata c
25
24
  ) external onlyAdmin(c.account) returns (bytes memory) {
26
- (Cur memory request, , ) = Cursors.init(c.request, 1);
25
+ (Cur memory input, ) = openInput(c.input, descriptor);
27
26
 
28
- while (request.i < request.len) {
29
- bytes32 account = request.unpackAccount();
27
+ while (input.i < input.len) {
28
+ bytes32 account = input.unpackAccount();
30
29
  setGuardian(account, true);
31
30
  }
32
-
33
- request.complete();
34
31
  return "";
35
32
  }
36
33
  }
@@ -2,7 +2,7 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import { AdminBase, CommandContext, Keys } from "./Base.sol";
5
- import { Cursors, Cur, Schemas } from "../../Cursors.sol";
5
+ import { Cursors, Cur } from "../../Cursors.sol";
6
6
  using Cursors for Cur;
7
7
 
8
8
  /// @title Authorize
@@ -10,27 +10,24 @@ using Cursors for Cur;
10
10
  /// Each NODE block in the request is authorized on the host.
11
11
  /// Only callable by the admin account.
12
12
  abstract contract Authorize is AdminBase {
13
- uint internal immutable authorizeId = commandId(this.authorize.selector);
13
+ bytes32 private immutable descriptor;
14
14
 
15
15
  constructor() {
16
- emit Admin(host, authorizeId, "1:0:0", Schemas.Node, Keys.Empty, Keys.Empty, false);
17
- emit Labeled(authorizeId, bytes32(0), "authorize");
16
+ (, descriptor) = command("authorize", Keys.Empty, Keys.Node, Keys.Empty, 0, false, true);
18
17
  }
19
18
 
20
19
  /// @notice Authorize each NODE block in the admin request.
21
- /// @param c Admin command context; `c.request` must contain NODE blocks.
20
+ /// @param c Admin command context; `c.input` must contain NODE blocks.
22
21
  /// @return Empty output state.
23
22
  function authorize(
24
23
  CommandContext calldata c
25
24
  ) external onlyAdmin(c.account) returns (bytes memory) {
26
- (Cur memory request, , ) = Cursors.init(c.request, 1);
25
+ (Cur memory input, ) = openInput(c.input, descriptor);
27
26
 
28
- while (request.i < request.len) {
29
- uint node = request.unpackNode();
27
+ while (input.i < input.len) {
28
+ uint node = input.unpackNode();
30
29
  setNode(node, true);
31
30
  }
32
-
33
- request.complete();
34
31
  return "";
35
32
  }
36
33
  }
@@ -2,8 +2,7 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import {CommandBase, CommandContext, Keys} from "../Base.sol";
5
- import {AdminEvent} from "../../events/Admin.sol";
6
5
 
7
6
  /// @title AdminBase
8
7
  /// @notice Shared base for admin commands.
9
- abstract contract AdminBase is CommandBase, AdminEvent {}
8
+ abstract contract AdminBase is CommandBase {}
@@ -2,7 +2,7 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import { AdminBase, CommandContext, Keys } from "./Base.sol";
5
- import { Cursors, Cur, Schemas } from "../../Cursors.sol";
5
+ import { Cursors, Cur } from "../../Cursors.sol";
6
6
  using Cursors for Cur;
7
7
 
8
8
  abstract contract DenyAssetsHook {
@@ -16,27 +16,24 @@ abstract contract DenyAssetsHook {
16
16
  /// @notice Admin command that blocks a list of assets via a virtual hook.
17
17
  /// Each ASSET block in the request calls `denyAsset`. Only callable by the admin account.
18
18
  abstract contract DenyAssets is AdminBase, DenyAssetsHook {
19
- uint internal immutable denyAssetsId = commandId(this.denyAssets.selector);
19
+ bytes32 private immutable descriptor;
20
20
 
21
21
  constructor() {
22
- emit Admin(host, denyAssetsId, "1:0:0", Schemas.Asset, Keys.Empty, Keys.Empty, false);
23
- emit Labeled(denyAssetsId, bytes32(0), "denyAssets");
22
+ (, descriptor) = command("denyAssets", Keys.Empty, Keys.Asset, Keys.Empty, 0, false, true);
24
23
  }
25
24
 
26
25
  /// @notice Deny each ASSET block in the admin request.
27
- /// @param c Admin command context; `c.request` must contain ASSET blocks.
26
+ /// @param c Admin command context; `c.input` must contain ASSET blocks.
28
27
  /// @return Empty output state.
29
28
  function denyAssets(
30
29
  CommandContext calldata c
31
30
  ) external onlyAdmin(c.account) returns (bytes memory) {
32
- (Cur memory request, , ) = Cursors.init(c.request, 1);
31
+ (Cur memory input, ) = openInput(c.input, descriptor);
33
32
 
34
- while (request.i < request.len) {
35
- bytes32 asset = request.unpackAsset();
33
+ while (input.i < input.len) {
34
+ bytes32 asset = input.unpackAsset();
36
35
  denyAsset(asset);
37
36
  }
38
-
39
- request.complete();
40
37
  return "";
41
38
  }
42
39
  }
@@ -2,7 +2,7 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import { AdminBase, CommandContext, Keys } from "./Base.sol";
5
- import { Cursors, Cur, Schemas } from "../../Cursors.sol";
5
+ import { Cursors, Cur } from "../../Cursors.sol";
6
6
  using Cursors for Cur;
7
7
 
8
8
  /// @title Dismiss
@@ -10,27 +10,24 @@ using Cursors for Cur;
10
10
  /// Each ACCOUNT block in the request is disabled as a guardian on the host.
11
11
  /// Only callable by the admin account.
12
12
  abstract contract Dismiss is AdminBase {
13
- uint internal immutable dismissId = commandId(this.dismiss.selector);
13
+ bytes32 private immutable descriptor;
14
14
 
15
15
  constructor() {
16
- emit Admin(host, dismissId, "1:0:0", Schemas.Account, Keys.Empty, Keys.Empty, false);
17
- emit Labeled(dismissId, bytes32(0), "dismiss");
16
+ (, descriptor) = command("dismiss", Keys.Empty, Keys.Account, Keys.Empty, 0, false, true);
18
17
  }
19
18
 
20
19
  /// @notice Dismiss each ACCOUNT block in the admin request from guardian status.
21
- /// @param c Admin command context; `c.request` must contain ACCOUNT blocks.
20
+ /// @param c Admin command context; `c.input` must contain ACCOUNT blocks.
22
21
  /// @return Empty output state.
23
22
  function dismiss(
24
23
  CommandContext calldata c
25
24
  ) external onlyAdmin(c.account) returns (bytes memory) {
26
- (Cur memory request, , ) = Cursors.init(c.request, 1);
25
+ (Cur memory input, ) = openInput(c.input, descriptor);
27
26
 
28
- while (request.i < request.len) {
29
- bytes32 account = request.unpackAccount();
27
+ while (input.i < input.len) {
28
+ bytes32 account = input.unpackAccount();
30
29
  setGuardian(account, false);
31
30
  }
32
-
33
- request.complete();
34
31
  return "";
35
32
  }
36
33
  }
@@ -3,7 +3,7 @@ pragma solidity ^0.8.33;
3
3
 
4
4
  import {AdminBase, CommandContext, Keys} from "./Base.sol";
5
5
  import {Payable} from "../../core/Payable.sol";
6
- import {Cursors, Cur, Schemas} from "../../Cursors.sol";
6
+ import {Cursors, Cur} from "../../Cursors.sol";
7
7
  import {Budget} from "../../utils/Value.sol";
8
8
 
9
9
  using Cursors for Cur;
@@ -14,26 +14,23 @@ using Cursors for Cur;
14
14
  /// Only callable by the admin account.
15
15
  /// Unspent top-level `msg.value` remains on this host.
16
16
  abstract contract ExecutePayable is AdminBase, Payable {
17
- uint internal immutable executePayableId = commandId(this.executePayable.selector);
17
+ bytes32 private immutable descriptor;
18
18
 
19
19
  constructor() {
20
- emit Admin(host, executePayableId, "1:0:0", Schemas.Call, Keys.Empty, Keys.Empty, true);
21
- emit Labeled(executePayableId, bytes32(0), "executePayable");
20
+ (, descriptor) = command("executePayable", Keys.Empty, Keys.Call, Keys.Empty, 0, true, true);
22
21
  }
23
22
 
24
23
  /// @notice Execute each CALL block in the admin request.
25
- /// @param c Admin command context; `c.request` must contain CALL blocks.
24
+ /// @param c Admin command context; `c.input` must contain CALL blocks.
26
25
  /// @return Empty output state.
27
26
  function executePayable(CommandContext calldata c) external payable onlyAdmin(c.account) returns (bytes memory) {
28
- (Cur memory request, , ) = Cursors.init(c.request, 1);
27
+ (Cur memory input, ) = openInput(c.input, descriptor);
29
28
  Budget memory budget = openValue();
30
29
 
31
- while (request.i < request.len) {
32
- (uint target, uint resources, bytes calldata data) = request.unpackCall();
30
+ while (input.i < input.len) {
31
+ (uint target, uint resources, bytes calldata data) = input.unpackCall();
33
32
  rawCall(target, useValue(budget, resources), data);
34
33
  }
35
-
36
- request.complete();
37
34
  return "";
38
35
  }
39
36
  }
@@ -2,7 +2,7 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import {AdminBase, CommandContext, Keys} from "./Base.sol";
5
- import {Cursors, Cur, Schemas} from "../../Cursors.sol";
5
+ import {Cursors, Cur} from "../../Cursors.sol";
6
6
  using Cursors for Cur;
7
7
 
8
8
  /// @title Label
@@ -10,25 +10,22 @@ using Cursors for Cur;
10
10
  /// Each LABEL block in the request emits one `Labeled` event. Only callable by
11
11
  /// the admin account.
12
12
  abstract contract Label is AdminBase {
13
- uint internal immutable labelId = commandId(this.label.selector);
13
+ bytes32 private immutable descriptor;
14
14
 
15
15
  constructor() {
16
- emit Admin(host, labelId, "1:0:0", Schemas.Label, Keys.Empty, Keys.Empty, false);
17
- emit Labeled(labelId, bytes32(0), "label");
16
+ (, descriptor) = command("label", Keys.Empty, Keys.Label, Keys.Empty, 0, false, true);
18
17
  }
19
18
 
20
19
  /// @notice Publish each LABEL block in the admin request.
21
- /// @param c Admin command context; `c.request` must contain LABEL blocks.
20
+ /// @param c Admin command context; `c.input` must contain LABEL blocks.
22
21
  /// @return Empty output state.
23
22
  function label(CommandContext calldata c) external onlyAdmin(c.account) returns (bytes memory) {
24
- (Cur memory request, , ) = Cursors.init(c.request, 1);
23
+ (Cur memory input, ) = openInput(c.input, descriptor);
25
24
 
26
- while (request.i < request.len) {
27
- (uint id, bytes32 namespace, string memory name) = request.unpackLabel();
28
- emit Labeled(id, namespace, name);
25
+ while (input.i < input.len) {
26
+ (uint node, bytes32 namespace, string memory name) = input.unpackLabel();
27
+ emit Labeled(node, namespace, name);
29
28
  }
30
-
31
- request.complete();
32
29
  return "";
33
30
  }
34
31
  }
@@ -2,7 +2,7 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import { AdminBase, CommandContext, Keys } from "./Base.sol";
5
- import { Cursors, Cur, Schemas } from "../../Cursors.sol";
5
+ import { Cursors, Cur } from "../../Cursors.sol";
6
6
  using Cursors for Cur;
7
7
 
8
8
  /// @title Unauthorize
@@ -10,27 +10,24 @@ using Cursors for Cur;
10
10
  /// Each NODE block in the request is deauthorized on the host.
11
11
  /// Only callable by the admin account.
12
12
  abstract contract Unauthorize is AdminBase {
13
- uint internal immutable unauthorizeId = commandId(this.unauthorize.selector);
13
+ bytes32 private immutable descriptor;
14
14
 
15
15
  constructor() {
16
- emit Admin(host, unauthorizeId, "1:0:0", Schemas.Node, Keys.Empty, Keys.Empty, false);
17
- emit Labeled(unauthorizeId, bytes32(0), "unauthorize");
16
+ (, descriptor) = command("unauthorize", Keys.Empty, Keys.Node, Keys.Empty, 0, false, true);
18
17
  }
19
18
 
20
19
  /// @notice Unauthorize each NODE block in the admin request.
21
- /// @param c Admin command context; `c.request` must contain NODE blocks.
20
+ /// @param c Admin command context; `c.input` must contain NODE blocks.
22
21
  /// @return Empty output state.
23
22
  function unauthorize(
24
23
  CommandContext calldata c
25
24
  ) external onlyAdmin(c.account) returns (bytes memory) {
26
- (Cur memory request, , ) = Cursors.init(c.request, 1);
25
+ (Cur memory input, ) = openInput(c.input, descriptor);
27
26
 
28
- while (request.i < request.len) {
29
- uint node = request.unpackNode();
27
+ while (input.i < input.len) {
28
+ uint node = input.unpackNode();
30
29
  setNode(node, false);
31
30
  }
32
-
33
- request.complete();
34
31
  return "";
35
32
  }
36
33
  }
@@ -0,0 +1,153 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import {Cursors, Cur} from "../Cursors.sol";
5
+ import {Keys} from "../blocks/Keys.sol";
6
+ import {EndpointEvent} from "../events/Endpoint.sol";
7
+ import {LabeledEvent} from "../events/Labeled.sol";
8
+ import {SchemaEvent} from "../events/Schema.sol";
9
+ import {Runtime} from "./Runtime.sol";
10
+
11
+ /// @title Lane
12
+ /// @notice Bit offsets for endpoint descriptor lanes.
13
+ library Lane {
14
+ /// @dev Descriptor shift for the state lane.
15
+ uint internal constant State = 184;
16
+ /// @dev Descriptor shift for the input lane.
17
+ uint internal constant Input = 112;
18
+ /// @dev Descriptor shift for the output lane.
19
+ uint internal constant Output = 40;
20
+ }
21
+
22
+ /// @title EndpointBase
23
+ /// @notice Shared endpoint metadata helpers.
24
+ abstract contract EndpointBase is Runtime, EndpointEvent, LabeledEvent, SchemaEvent {
25
+ /// @dev Pack endpoint lanes and flags into a descriptor.
26
+ /// A non-empty lane with group 0 defaults to group 1; a zero lane is absent.
27
+ /// Layout: `[state:8][group:1][input:8][group:1][output:8][group:1]`
28
+ /// `[flags:1][reserved:4]`. Flag bits: funded = 0, admin = 1.
29
+ /// @param state Packed state lane plus optional group byte.
30
+ /// @param input Packed input lane plus optional group byte.
31
+ /// @param output Packed output lane plus optional group byte.
32
+ /// @param funded Whether the endpoint accepts nonzero native value.
33
+ /// @param admin Whether the endpoint is restricted to the admin account.
34
+ /// @return value Packed endpoint descriptor as an integer.
35
+ function pack(
36
+ bytes9 state,
37
+ bytes9 input,
38
+ bytes9 output,
39
+ bool funded,
40
+ bool admin
41
+ ) private pure returns (uint value) {
42
+ value |= uint(uint72(state)) << Lane.State;
43
+ value |= uint(uint72(input)) << Lane.Input;
44
+ value |= uint(uint72(output)) << Lane.Output;
45
+ value |= uint(funded ? 1 : 0) << 32;
46
+ value |= uint(admin ? 1 : 0) << 33;
47
+ }
48
+
49
+ /// @dev Return a lane's effective group size, defaulting non-empty lanes to one.
50
+ /// @param descriptor Packed endpoint descriptor.
51
+ /// @param shift Bit offset of the lane to inspect.
52
+ /// @return size Effective group size, or zero when the lane is absent.
53
+ function laneGroup(bytes32 descriptor, uint shift) private pure returns (uint8 size) {
54
+ uint72 lane = uint72(uint(descriptor) >> shift);
55
+ if (lane == 0) return 0;
56
+
57
+ size = uint8(lane);
58
+ if (size == 0) size = 1;
59
+ }
60
+
61
+ /// @notice Return an 8-byte lane value for a generic LIST containing `item`.
62
+ /// @param item Block key expected inside each LIST payload.
63
+ /// @return Packed lane key `[Keys.List][item]`.
64
+ function many(bytes4 item) internal pure returns (bytes8) {
65
+ return bytes8(bytes.concat(Keys.List, item));
66
+ }
67
+
68
+ /// @notice Append an explicit group size to an 8-byte lane value.
69
+ /// @param value Packed lane key `[key][item]`.
70
+ /// @param size Explicit per-operation group size for the lane.
71
+ /// @return Packed lane key plus group byte.
72
+ function group(bytes8 value, uint8 size) internal pure returns (bytes9) {
73
+ return bytes9(bytes.concat(value, bytes1(size)));
74
+ }
75
+
76
+ /// @dev Open a descriptor lane and return its effective group and output counts.
77
+ /// An absent lane inherits `expected`; a present lane must match it when nonzero.
78
+ /// @param source Block stream to open for the requested lane.
79
+ /// @param descriptor Packed endpoint descriptor.
80
+ /// @param shift Bit offset of the lane to open.
81
+ /// @param expected Required group count, or zero to accept the lane's count.
82
+ /// @return cur Cursor scoped to the lane's first block run.
83
+ /// @return groups Number of lane groups in `cur`, or `expected` for an absent lane.
84
+ /// @return outputs Number of output blocks implied by `groups` and the descriptor output lane.
85
+ function openLane(
86
+ bytes calldata source,
87
+ bytes32 descriptor,
88
+ uint shift,
89
+ uint expected
90
+ ) internal pure returns (Cur memory cur, uint groups, uint outputs) {
91
+ (cur, groups) = Cursors.init(source, laneGroup(descriptor, shift));
92
+ if (groups == 0) groups = expected;
93
+ else if (expected != 0 && groups != expected) revert Cursors.BadRatio();
94
+ outputs = groups * laneGroup(descriptor, Lane.Output);
95
+ }
96
+
97
+ /// @notice Open an endpoint state stream and return the expected output block count.
98
+ /// @param source State block stream to open.
99
+ /// @param descriptor Packed endpoint descriptor.
100
+ /// @return state Cursor scoped to the state lane's first block run.
101
+ /// @return outputs Number of output blocks implied by the state group count.
102
+ function openState(
103
+ bytes calldata source,
104
+ bytes32 descriptor
105
+ ) internal pure returns (Cur memory state, uint outputs) {
106
+ (state, , outputs) = openLane(source, descriptor, Lane.State, 0);
107
+ }
108
+
109
+ /// @notice Open an endpoint input stream and return the expected output block count.
110
+ /// @param source Input block stream to open.
111
+ /// @param descriptor Packed endpoint descriptor.
112
+ /// @return input Cursor scoped to the input lane's first block run.
113
+ /// @return outputs Number of output blocks implied by the input group count.
114
+ function openInput(
115
+ bytes calldata source,
116
+ bytes32 descriptor
117
+ ) internal pure returns (Cur memory input, uint outputs) {
118
+ (input, , outputs) = openLane(source, descriptor, Lane.Input, 0);
119
+ }
120
+
121
+ /// @notice Publish a block schema and return its key for descriptor construction.
122
+ /// @param key Block key being defined.
123
+ /// @param body Schema DSL string describing the block payload body.
124
+ /// @param name Optional block alias used by descriptor tooling and nested schemas.
125
+ /// @return The same block key, for inline descriptor construction.
126
+ function schema(bytes4 key, string memory body, bytes32 name) internal returns (bytes4) {
127
+ emit Schema(host, key, body, name);
128
+ return key;
129
+ }
130
+
131
+ /// @notice Create and publish endpoint metadata with a default label.
132
+ /// @param id Endpoint node ID.
133
+ /// @param name Default human-readable endpoint label.
134
+ /// @param state Packed state lane plus optional group byte.
135
+ /// @param input Packed input lane plus optional group byte.
136
+ /// @param output Packed output lane plus optional group byte.
137
+ /// @param funded Whether the endpoint accepts nonzero native value.
138
+ /// @param admin Whether the endpoint is restricted to the admin account.
139
+ /// @return descriptor Packed endpoint lane metadata and flags.
140
+ function endpoint(
141
+ uint id,
142
+ string memory name,
143
+ bytes9 state,
144
+ bytes9 input,
145
+ bytes9 output,
146
+ bool funded,
147
+ bool admin
148
+ ) internal returns (bytes32 descriptor) {
149
+ descriptor = bytes32(pack(state, input, output, funded, admin));
150
+ emit Endpoint(host, id, descriptor);
151
+ emit Labeled(id, bytes32(0), name);
152
+ }
153
+ }