@rootzero/contracts 1.9.0 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/CHANGELOG.md +40 -2
  2. package/Core.sol +3 -1
  3. package/Endpoints.sol +3 -6
  4. package/Events.sol +3 -6
  5. package/README.md +35 -33
  6. package/Utils.sol +2 -1
  7. package/blocks/Cursors.sol +39 -88
  8. package/blocks/Keys.sol +14 -4
  9. package/blocks/Schema.sol +49 -44
  10. package/commands/Base.sol +43 -13
  11. package/commands/Burn.sol +3 -6
  12. package/commands/Credit.sol +9 -6
  13. package/commands/Debit.sol +15 -11
  14. package/commands/Deposit.sol +19 -23
  15. package/commands/Payout.sol +6 -10
  16. package/commands/Provision.sol +19 -23
  17. package/commands/Recover.sol +16 -10
  18. package/commands/Relay.sol +21 -12
  19. package/commands/Withdraw.sol +3 -6
  20. package/commands/admin/AllowAssets.sol +7 -10
  21. package/commands/admin/Allowance.sol +7 -10
  22. package/commands/admin/Appoint.sol +7 -10
  23. package/commands/admin/Authorize.sol +7 -10
  24. package/commands/admin/Base.sol +1 -2
  25. package/commands/admin/DenyAssets.sol +7 -10
  26. package/commands/admin/Dismiss.sol +7 -10
  27. package/commands/admin/Execute.sol +7 -10
  28. package/commands/admin/Label.sol +8 -11
  29. package/commands/admin/Unauthorize.sol +7 -10
  30. package/core/Endpoint.sol +153 -0
  31. package/core/Payable.sol +7 -0
  32. package/core/Pipeline.sol +1 -1
  33. package/core/Portal.sol +19 -48
  34. package/docs/Schema.md +114 -82
  35. package/events/Endpoint.sol +19 -0
  36. package/events/Recovered.sol +17 -0
  37. package/events/Schema.sol +23 -0
  38. package/guards/Base.sol +17 -8
  39. package/guards/Revoke.sol +4 -6
  40. package/package.json +1 -1
  41. package/ports/AllowAssets.sol +6 -9
  42. package/ports/Allowance.sol +6 -9
  43. package/ports/Base.sol +21 -8
  44. package/ports/Credit.sol +6 -9
  45. package/ports/Debit.sol +6 -9
  46. package/ports/DenyAssets.sol +6 -9
  47. package/ports/Dispatch.sol +7 -10
  48. package/ports/Pipe.sol +4 -7
  49. package/ports/Redeem.sol +4 -7
  50. package/ports/Settle.sol +6 -9
  51. package/queries/Assets.sol +9 -12
  52. package/queries/Balances.sol +7 -9
  53. package/queries/Base.sol +19 -11
  54. package/utils/Selectors.sol +49 -0
  55. package/commands/admin/Destroy.sol +0 -43
  56. package/commands/admin/Init.sol +0 -43
  57. package/events/Admin.sol +0 -32
  58. package/events/Command.sol +0 -32
  59. package/events/Guard.sol +0 -18
  60. package/events/Port.sol +0 -22
  61. package/events/Query.sol +0 -20
  62. package/events/Resolved.sol +0 -17
  63. package/queries/Positions.sol +0 -54
package/blocks/Schema.sol CHANGED
@@ -7,31 +7,36 @@ pragma solidity ^0.8.33;
7
7
  // - payload layout is block-specific
8
8
  //
9
9
  // Schema:
10
- // - blocks are written as `#name { fields }`
11
- // - a block without braces has no payload, e.g. `#unit`
10
+ // - block aliases are published separately from payload schemas
11
+ // - payload schemas are written as `{ fields }`
12
+ // - an empty schema string means the block has no structured payload
12
13
  // - commas separate siblings at every level
13
- // - braces define parent-child boundaries
14
+ // - braces define the current block payload body
14
15
  // - command requests are a single run when the request schema is non-empty
15
16
  // - command state is a single active state run without trailing globals
16
17
  // - run items may repeat at top level for batching
17
- // - `maybe #x { ... }` marks an optional block item
18
- // - `many #x { ... }` emits one generic list block containing repeated `#x` items
18
+ // - `maybe #x` marks an optional block item
19
+ // - `many #x` emits one generic list block containing repeated `#x` items
20
+ // - endpoint descriptor lanes are `[key bytes4][item bytes4]`; normal keys widen to `[key][0]`
21
+ // - descriptor lanes for `many #x` use `[Keys.List][keyOfX]`; bare `[Keys.List][0]`
22
+ // is incomplete discovery metadata and should be rejected by tooling
19
23
  // - `portal` fields are routing identifiers, often destination host IDs
20
24
  // - `resources` fields are chain-specific resource words. A portal adapter
21
25
  // interprets them for the destination runtime. EVM resources use the low
22
26
  // 128 bits as native value.
23
27
  // - dotted field names and aliases, e.g. `dst.portal` or `#bytes as dst.payload`,
24
28
  // are offchain projection metadata only and do not change runtime encoding
25
- // - a child block without an inline body, e.g. `#context as witness`, may resolve
26
- // to a known schema in the active schema context; unresolved aliases are invalid
29
+ // - child blocks resolve by alias in the active schema context; unresolved aliases are invalid
30
+ // - schema strings describe the payload body only; the `Block` event carries the alias
27
31
  // - fixed fields are packed in declaration order
28
32
  // - blocks have fixed fields followed by a dynamic child-block tail
29
33
  // - child block tails are embedded directly, without an extra stream wrapper
30
34
  // - `#bytes` is a reserved child block that stores raw bytes and has no body
31
35
  // - `#string` is a reserved child block that stores UTF-8 string bytes and has no body
32
- // - generic `#data` uses the stable key derived from `#data`
33
36
  // - generic lists use the stable key derived from `#list`
34
- // - keys are derived from block names, e.g. bytes4(keccak256("#amount"))
37
+ // - standard keys are derived from block aliases, e.g. bytes4(keccak256("#amount"))
38
+ // - custom keys are opaque bytes4 tags and only need to be unique in their
39
+ // active context; use `Schema(host, key, schema, name)` to publish their meaning
35
40
  // - see `docs/Schema.md` for the full working spec
36
41
  //
37
42
  // Pipeline state:
@@ -57,48 +62,48 @@ pragma solidity ^0.8.33;
57
62
  /// @title Schemas
58
63
  /// @notice Human-readable schema string constants for each block type.
59
64
  /// These strings describe payload layout for discovery events and docs; block
60
- /// keys are derived only from block names.
65
+ /// aliases map to standard keys by convention. Custom blocks may use any unique
66
+ /// bytes4 key in their active context.
61
67
  library Schemas {
62
- string constant Unit = "#unit";
63
- string constant Node = "#node { uint id }";
64
- string constant Account = "#account { bytes32 account }";
65
- string constant Asset = "#asset { bytes32 asset }";
66
- string constant Amount = "#amount { bytes32 asset, uint amount }";
67
- string constant Balance = "#balance { bytes32 asset, uint amount }";
68
- string constant BalanceLimit = "#balanceLimit { bytes32 asset, uint min, uint max }";
69
- string constant Custody = "#custody { uint host, bytes32 asset, uint amount }";
70
- string constant CustodyLimit = "#custodyLimit { uint host, bytes32 asset, uint min, uint max }";
71
- string constant Allocation = "#allocation { uint host, bytes32 asset, uint amount }";
72
- string constant Allowance = "#allowance { uint host, bytes32 asset, uint amount }";
73
- string constant Transaction = "#transaction { bytes32 from, bytes32 to, bytes32 asset, uint amount }";
74
- string constant Context = "#context { bytes32 account, #bytes as state, #bytes as request }";
75
- string constant Recover = "#recover { uint handler, uint resources, bytes32 key, #bytes as witness }";
76
- string constant Call = "#call { uint target, uint resources, #bytes as payload }";
77
- string constant Step = "#step { uint target, uint resources, #bytes as request }";
78
- string constant Relay = "#relay { uint portal, uint resources, #bytes as request }";
79
- string constant Dispatch = "#dispatch { uint portal, uint resources, #bytes as payload }";
80
- string constant Bounty = "#bounty { uint amount, bytes32 relayer }";
81
- string constant Fee = "#fee { uint amount }";
82
- string constant Auth = "#auth { uint cid, uint deadline, #bytes as proof }";
83
- string constant Label = "#label { uint id, bytes32 namespace, #string as name }";
84
- string constant Bytes = "#bytes";
85
- string constant String = "#string";
86
- string constant Data = "#data";
87
- string constant List = "#list";
88
- string constant Evm = "#evm";
68
+ string constant Unit = "";
69
+ string constant Node = "{ uint id }";
70
+ string constant Account = "{ bytes32 account }";
71
+ string constant Asset = "{ bytes32 asset }";
72
+ string constant Amount = "{ bytes32 asset, uint amount }";
73
+ string constant Balance = "{ bytes32 asset, uint amount }";
74
+ string constant BalanceLimit = "{ bytes32 asset, uint min, uint max }";
75
+ string constant Custody = "{ uint host, bytes32 asset, uint amount }";
76
+ string constant CustodyLimit = "{ uint host, bytes32 asset, uint min, uint max }";
77
+ string constant Allocation = "{ uint host, bytes32 asset, uint amount }";
78
+ string constant Allowance = "{ uint host, bytes32 asset, uint amount }";
79
+ string constant Transaction = "{ bytes32 from, bytes32 to, bytes32 asset, uint amount }";
80
+ string constant Context = "{ bytes32 account, #bytes as state, #bytes as request }";
81
+ string constant Recover = "{ uint handler, uint resources, bytes32 key, #bytes as witness }";
82
+ string constant Call = "{ uint target, uint resources, #bytes as payload }";
83
+ string constant Step = "{ uint target, uint resources, #bytes as request }";
84
+ string constant Relay = "{ uint portal, uint resources, #bytes as request }";
85
+ string constant Dispatch = "{ uint portal, uint resources, #bytes as payload }";
86
+ string constant Bounty = "{ uint amount, bytes32 relayer }";
87
+ string constant Fee = "{ uint amount }";
88
+ string constant Auth = "{ uint cid, uint deadline, #bytes as proof }";
89
+ string constant Label = "{ uint id, bytes32 namespace, #string as name }";
90
+ string constant Bytes = "";
91
+ string constant String = "";
92
+ string constant List = "";
93
+ string constant Evm = "";
89
94
  }
90
95
 
91
96
  /// @title Forms
92
97
  /// @notice Reusable structural block schemas for core tuple shapes.
93
98
  /// These describe payload form without assigning command or query semantics.
94
99
  library Forms {
95
- string constant Status = "#status { uint code }";
96
- string constant AssetAmount = "#assetAmount { bytes32 asset, uint amount }";
97
- string constant AccountAsset = "#accountAsset { bytes32 account, bytes32 asset }";
98
- string constant AccountAmount = "#accountAmount { bytes32 account, bytes32 asset, uint amount }";
99
- string constant HostAmount = "#hostAmount { uint host, bytes32 asset, uint amount }";
100
- string constant HostAccountAsset = "#hostAccountAsset { uint host, bytes32 account, bytes32 asset }";
101
- string constant HostAccountAmount = "#hostAccountAmount { uint host, bytes32 account, bytes32 asset, uint amount }";
100
+ string constant Status = "{ uint code }";
101
+ string constant AssetAmount = "{ bytes32 asset, uint amount }";
102
+ string constant AccountAsset = "{ bytes32 account, bytes32 asset }";
103
+ string constant AccountAmount = "{ bytes32 account, bytes32 asset, uint amount }";
104
+ string constant HostAmount = "{ uint host, bytes32 asset, uint amount }";
105
+ string constant HostAccountAsset = "{ uint host, bytes32 account, bytes32 asset }";
106
+ string constant HostAccountAmount = "{ uint host, bytes32 account, bytes32 asset, uint amount }";
102
107
  }
103
108
 
104
109
  /// @title Sizes
package/commands/Base.sol CHANGED
@@ -2,10 +2,11 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import {NodeCalls} from "../core/Calls.sol";
5
- import {CommandEvent} from "../events/Command.sol";
6
- import {LabeledEvent} from "../events/Labeled.sol";
5
+ import {EndpointBase, Lane} from "../core/Endpoint.sol";
6
+ import {Cur} from "../Cursors.sol";
7
7
  import {Keys} from "../blocks/Keys.sol";
8
8
  import {Nodes} from "../utils/Nodes.sol";
9
+ import {Selectors} from "../utils/Selectors.sol";
9
10
 
10
11
  /// @notice Execution context passed to every command invocation.
11
12
  struct CommandContext {
@@ -14,14 +15,13 @@ struct CommandContext {
14
15
  /// @dev Current state block stream (previous command output or initial state).
15
16
  bytes state;
16
17
  /// @dev Input block stream for this invocation.
17
- bytes request;
18
+ bytes input;
18
19
  }
19
20
 
20
21
  /// @title CommandBase
21
22
  /// @notice Abstract base for all rootzero command contracts.
22
- /// Provides access control modifiers, event emission, and the `commandId`
23
- /// helper used to derive stable identifiers for command selectors.
24
- abstract contract CommandBase is NodeCalls, CommandEvent, LabeledEvent {
23
+ /// Provides access control modifiers and command endpoint metadata helpers.
24
+ abstract contract CommandBase is NodeCalls, EndpointBase {
25
25
  /// @dev Thrown when `onlyActive` finds that `deadline` has already passed.
26
26
  error Expired();
27
27
 
@@ -45,12 +45,42 @@ abstract contract CommandBase is NodeCalls, CommandEvent, LabeledEvent {
45
45
  _;
46
46
  }
47
47
 
48
- /// @notice Derive the deterministic node ID for a command selector on this contract.
49
- /// The ID encodes the ABI selector and `address(this)`, making it unique
50
- /// per (function selector, contract address) pair.
51
- /// @param selector Command entrypoint selector.
52
- /// @return Command node ID.
53
- function commandId(bytes4 selector) internal view returns (uint) {
54
- return Nodes.toCommand(selector, address(this));
48
+ /// @notice Publish command metadata and a default label.
49
+ /// @param name Default human-readable command label and selector name.
50
+ /// @param state Packed state lane plus optional group byte.
51
+ /// @param input Packed input lane plus optional group byte.
52
+ /// @param output Packed output lane plus optional group byte.
53
+ /// @param selector Command ABI selector, or zero to derive it from `name`.
54
+ /// @param funded Whether the command accepts nonzero native value.
55
+ /// @param admin Whether the command is restricted to the admin account.
56
+ /// @return id Command node ID.
57
+ /// @return descriptor Packed endpoint lane metadata and flags.
58
+ function command(
59
+ string memory name,
60
+ bytes9 state,
61
+ bytes9 input,
62
+ bytes9 output,
63
+ bytes4 selector,
64
+ bool funded,
65
+ bool admin
66
+ ) internal returns (uint id, bytes32 descriptor) {
67
+ selector = selector == bytes4(0) ? Selectors.command(name) : selector;
68
+ id = Nodes.toCommand(selector, address(this));
69
+ descriptor = endpoint(id, name, state, input, output, funded, admin);
70
+ }
71
+
72
+ /// @notice Open input/state cursors and return the expected output block count.
73
+ /// @param c Command invocation context.
74
+ /// @param descriptor Packed command endpoint descriptor.
75
+ /// @return input Cursor scoped to the command input lane.
76
+ /// @return state Cursor scoped to the command state lane.
77
+ /// @return outputs Number of output blocks implied by the matched group count.
78
+ function openCommand(
79
+ CommandContext calldata c,
80
+ bytes32 descriptor
81
+ ) internal pure returns (Cur memory input, Cur memory state, uint outputs) {
82
+ uint groups;
83
+ (input, groups, ) = openLane(c.input, descriptor, Lane.Input, 0);
84
+ (state, , outputs) = openLane(c.state, descriptor, Lane.State, groups);
55
85
  }
56
86
  }
package/commands/Burn.sol CHANGED
@@ -19,25 +19,22 @@ abstract contract BurnHook {
19
19
  /// @notice Command that irreversibly destroys each BALANCE state block via a virtual hook.
20
20
  /// Produces no output state.
21
21
  abstract contract Burn is CommandBase, BurnHook {
22
- uint internal immutable burnId = commandId(this.burn.selector);
22
+ bytes32 private immutable descriptor;
23
23
 
24
24
  constructor() {
25
- emit Command(host, burnId, "0:1:0", "", Keys.Balance, Keys.Empty, false);
26
- emit Labeled(burnId, bytes32(0), "burn");
25
+ (, descriptor) = command("burn", Keys.Balance, Keys.Empty, Keys.Empty, 0, false, false);
27
26
  }
28
27
 
29
28
  /// @notice Burn each BALANCE block from the command state.
30
29
  /// @param c Command context; `c.state` must contain BALANCE blocks.
31
30
  /// @return Empty output state.
32
31
  function burn(CommandContext calldata c) external onlyCommand returns (bytes memory) {
33
- (Cur memory state, , ) = Cursors.init(c.state, 1);
32
+ (Cur memory state, ) = openState(c.state, descriptor);
34
33
 
35
34
  while (state.i < state.len) {
36
35
  (bytes32 asset, uint amount) = state.unpackBalance();
37
36
  burn(c.account, asset, amount);
38
37
  }
39
-
40
- state.complete();
41
38
  return "";
42
39
  }
43
40
  }
@@ -19,11 +19,16 @@ abstract contract CreditAccountHook {
19
19
  /// @notice Command that delivers BALANCE state blocks to an account via a virtual hook.
20
20
  /// Use for internally recording credits that have already been settled externally.
21
21
  abstract contract CreditAccount is CommandBase, CreditAccountHook {
22
- uint internal immutable creditAccountId = commandId(this.creditAccount.selector);
22
+ bytes32 private immutable descriptor;
23
+ uint private immutable id;
23
24
 
24
25
  constructor() {
25
- emit Command(host, creditAccountId, "0:1:0", "", Keys.Balance, Keys.Empty, false);
26
- emit Labeled(creditAccountId, bytes32(0), "creditAccount");
26
+ (id, descriptor) = command("creditAccount", Keys.Balance, Keys.Empty, Keys.Empty, 0, false, false);
27
+ }
28
+
29
+ /// @notice Return true if `candidate` is this command's credit account ID.
30
+ function isCreditAccount(uint candidate) internal view returns (bool) {
31
+ return candidate == id;
27
32
  }
28
33
 
29
34
  /// @notice Credit each BALANCE block from the command state to the command account.
@@ -32,14 +37,12 @@ abstract contract CreditAccount is CommandBase, CreditAccountHook {
32
37
  function creditAccount(
33
38
  CommandContext calldata c
34
39
  ) external onlyCommand returns (bytes memory) {
35
- (Cur memory state, , ) = Cursors.init(c.state, 1);
40
+ (Cur memory state, ) = openState(c.state, descriptor);
36
41
 
37
42
  while (state.i < state.len) {
38
43
  (bytes32 asset, uint amount) = state.unpackBalance();
39
44
  creditAccount(c.account, asset, amount);
40
45
  }
41
-
42
- state.complete();
43
46
  return "";
44
47
  }
45
48
  }
@@ -2,7 +2,7 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import { CommandContext, CommandBase, Keys } from "./Base.sol";
5
- import { Cursors, Cur, Schemas, Writer, Writers } from "../Cursors.sol";
5
+ import { Cursors, Cur, Writer, Writers } from "../Cursors.sol";
6
6
 
7
7
  using Cursors for Cur;
8
8
  using Writers for Writer;
@@ -21,37 +21,41 @@ abstract contract DebitAccountHook {
21
21
  /// Use for internally recording debits. The virtual `debitAccount` hook is called once per
22
22
  /// AMOUNT block; the default batch implementation handles the full request loop.
23
23
  abstract contract DebitAccount is CommandBase, DebitAccountHook {
24
- uint internal immutable debitAccountId = commandId(this.debitAccount.selector);
24
+ bytes32 private immutable descriptor;
25
+ uint private immutable id;
25
26
 
26
27
  constructor() {
27
- emit Command(host, debitAccountId, "1:0:1", Schemas.Amount, Keys.Empty, Keys.Balance, false);
28
- emit Labeled(debitAccountId, bytes32(0), "debitAccount");
28
+ (id, descriptor) = command("debitAccount", Keys.Empty, Keys.Amount, Keys.Balance, 0, false, false);
29
+ }
30
+
31
+ /// @notice Return true if `candidate` is this command's debit account ID.
32
+ function isDebitAccount(uint candidate) internal view returns (bool) {
33
+ return candidate == id;
29
34
  }
30
35
 
31
36
  /// @notice Override to customize request parsing or batching for debits.
32
37
  /// The default implementation iterates AMOUNT blocks, calls
33
38
  /// `debitAccount`, and emits matching BALANCE blocks.
34
39
  function debitAccount(bytes32 account, bytes calldata request) internal virtual returns (bytes memory) {
35
- (Cur memory input, uint groups, ) = Cursors.init(request, 1);
36
- Writer memory writer = Writers.allocBalances(groups);
40
+ (Cur memory input, uint outputs) = openInput(request, descriptor);
41
+ Writer memory output = Writers.allocBalances(outputs);
37
42
 
38
43
  while (input.i < input.len) {
39
44
  (bytes32 asset, uint amount) = input.unpackAmount();
40
45
  debitAccount(account, asset, amount);
41
- writer.appendBalance(asset, amount);
46
+ output.appendBalance(asset, amount);
42
47
  }
43
48
 
44
- input.complete();
45
- return writer.finish();
49
+ return output.finish();
46
50
  }
47
51
 
48
52
  /// @notice Debit AMOUNT request blocks from the command account and output matching BALANCE blocks.
49
- /// @param c Command context; `c.request` must contain AMOUNT blocks.
53
+ /// @param c Command context; `c.input` must contain AMOUNT blocks.
50
54
  /// @return BALANCE block stream matching the debited amounts.
51
55
  function debitAccount(
52
56
  CommandContext calldata c
53
57
  ) external onlyCommand returns (bytes memory) {
54
- return debitAccount(c.account, c.request);
58
+ return debitAccount(c.account, c.input);
55
59
  }
56
60
  }
57
61
 
@@ -3,7 +3,7 @@ pragma solidity ^0.8.33;
3
3
 
4
4
  import { CommandContext, CommandBase, Keys } from "./Base.sol";
5
5
  import { Payable } from "../core/Payable.sol";
6
- import { Cursors, Cur, Schemas, Writer, Writers } from "../Cursors.sol";
6
+ import { Cursors, Cur, Writer, Writers } from "../Cursors.sol";
7
7
  import { Budget } from "../utils/Value.sol";
8
8
 
9
9
  using Cursors for Cur;
@@ -35,30 +35,28 @@ abstract contract DepositPayableHook {
35
35
  /// Use `deposit` for assets arriving from outside the protocol (e.g. ERC-20 transfers, ETH).
36
36
  /// For internal balance deductions, use `debitAccount` instead.
37
37
  abstract contract Deposit is CommandBase, DepositHook {
38
- uint internal immutable depositId = commandId(this.deposit.selector);
38
+ bytes32 private immutable descriptor;
39
39
 
40
40
  constructor() {
41
- emit Command(host, depositId, "1:0:1", Schemas.Amount, Keys.Empty, Keys.Balance, false);
42
- emit Labeled(depositId, bytes32(0), "deposit");
41
+ (, descriptor) = command("deposit", Keys.Empty, Keys.Amount, Keys.Balance, 0, false, false);
43
42
  }
44
43
 
45
44
  /// @notice Deposit AMOUNT request blocks into the command account and output matching BALANCE blocks.
46
- /// @param c Command context; `c.request` must contain AMOUNT blocks.
45
+ /// @param c Command context; `c.input` must contain AMOUNT blocks.
47
46
  /// @return BALANCE block stream matching the deposited amounts.
48
47
  function deposit(
49
48
  CommandContext calldata c
50
49
  ) external onlyCommand returns (bytes memory) {
51
- (Cur memory request, uint groups, ) = Cursors.init(c.request, 1);
52
- Writer memory writer = Writers.allocBalances(groups);
50
+ (Cur memory input, uint outputs) = openInput(c.input, descriptor);
51
+ Writer memory output = Writers.allocBalances(outputs);
53
52
 
54
- while (request.i < request.len) {
55
- (bytes32 asset, uint amount) = request.unpackAmount();
53
+ while (input.i < input.len) {
54
+ (bytes32 asset, uint amount) = input.unpackAmount();
56
55
  deposit(c.account, asset, amount);
57
- writer.appendBalance(asset, amount);
56
+ output.appendBalance(asset, amount);
58
57
  }
59
58
 
60
- request.complete();
61
- return writer.finish();
59
+ return output.finish();
62
60
  }
63
61
  }
64
62
 
@@ -66,32 +64,30 @@ abstract contract Deposit is CommandBase, DepositHook {
66
64
  /// @notice Command that receives externally sourced assets and records them as BALANCE state.
67
65
  /// Use `depositPayable` when the hook needs tracked access to `msg.value` via a mutable budget.
68
66
  abstract contract DepositPayable is CommandBase, Payable, DepositPayableHook {
69
- uint internal immutable depositPayableId = commandId(this.depositPayable.selector);
67
+ bytes32 private immutable descriptor;
70
68
 
71
69
  constructor() {
72
- emit Command(host, depositPayableId, "1:0:1", Schemas.Amount, Keys.Empty, Keys.Balance, true);
73
- emit Labeled(depositPayableId, bytes32(0), "depositPayable");
70
+ (, descriptor) = command("depositPayable", Keys.Empty, Keys.Amount, Keys.Balance, 0, true, false);
74
71
  }
75
72
 
76
73
  /// @notice Deposit AMOUNT request blocks with access to a mutable native-value budget.
77
- /// @param c Command context; `c.request` must contain AMOUNT blocks.
74
+ /// @param c Command context; `c.input` must contain AMOUNT blocks.
78
75
  /// @return BALANCE block stream matching the deposited amounts.
79
76
  function depositPayable(
80
77
  CommandContext calldata c
81
78
  ) external payable onlyCommand returns (bytes memory) {
82
- (Cur memory request, uint groups, ) = Cursors.init(c.request, 1);
83
- Writer memory writer = Writers.allocBalances(groups);
79
+ (Cur memory input, uint outputs) = openInput(c.input, descriptor);
80
+ Writer memory output = Writers.allocBalances(outputs);
84
81
  Budget memory budget = openValue();
85
82
 
86
- while (request.i < request.len) {
87
- (bytes32 asset, uint amount) = request.unpackAmount();
83
+ while (input.i < input.len) {
84
+ (bytes32 asset, uint amount) = input.unpackAmount();
88
85
  deposit(c.account, asset, amount, budget);
89
- writer.appendBalance(asset, amount);
86
+ output.appendBalance(asset, amount);
90
87
  }
91
88
 
92
89
  closeValue(c.account, budget);
93
- request.complete();
94
- return writer.finish();
90
+ return output.finish();
95
91
  }
96
92
  }
97
93
 
@@ -2,7 +2,7 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import {CommandContext, CommandBase, Keys} from "./Base.sol";
5
- import {Cursors, Cur, Schemas} from "../Cursors.sol";
5
+ import {Cursors, Cur} from "../Cursors.sol";
6
6
 
7
7
  using Cursors for Cur;
8
8
 
@@ -20,26 +20,22 @@ abstract contract PayoutHook {
20
20
  /// @notice Command that sinks BALANCE state blocks to matching ACCOUNT request blocks.
21
21
  /// Each BALANCE block is paired with one ACCOUNT block at the same position.
22
22
  abstract contract Payout is CommandBase, PayoutHook {
23
- uint internal immutable payoutId = commandId(this.payout.selector);
23
+ bytes32 private immutable descriptor;
24
24
 
25
25
  constructor() {
26
- emit Command(host, payoutId, "1:1:0", Schemas.Account, Keys.Balance, Keys.Empty, false);
27
- emit Labeled(payoutId, bytes32(0), "payout");
26
+ (, descriptor) = command("payout", Keys.Balance, Keys.Account, Keys.Empty, 0, false, false);
28
27
  }
29
28
 
30
29
  /// @notice Pay out BALANCE state blocks to matching ACCOUNT request blocks.
31
- /// @param c Command context; `c.state` must contain BALANCE blocks and `c.request` matching ACCOUNT blocks.
30
+ /// @param c Command context; `c.state` must contain BALANCE blocks and `c.input` matching ACCOUNT blocks.
32
31
  /// @return Empty output state.
33
32
  function payout(CommandContext calldata c) external onlyCommand returns (bytes memory) {
34
- (Cur memory state, uint groups, ) = Cursors.init(c.state, 1);
35
- (Cur memory request, ) = Cursors.init(c.request, 1, groups);
33
+ (Cur memory input, Cur memory state, ) = openCommand(c, descriptor);
36
34
 
37
35
  while (state.i < state.len) {
38
36
  (bytes32 asset, uint amount) = state.unpackBalance();
39
- payout(c.account, request.unpackAccount(), asset, amount);
37
+ payout(c.account, input.unpackAccount(), asset, amount);
40
38
  }
41
-
42
- state.complete();
43
39
  return "";
44
40
  }
45
41
  }
@@ -3,7 +3,7 @@ pragma solidity ^0.8.33;
3
3
 
4
4
  import {CommandContext, CommandBase, Keys} from "./Base.sol";
5
5
  import {Payable} from "../core/Payable.sol";
6
- import {HostAmount, Cursors, Cur, Schemas, Writer, Writers} from "../Cursors.sol";
6
+ import {HostAmount, Cursors, Cur, Writer, Writers} from "../Cursors.sol";
7
7
  import {Budget} from "../utils/Value.sol";
8
8
  using Cursors for Cur;
9
9
  using Writers for Writer;
@@ -33,28 +33,26 @@ abstract contract ProvisionPayableHook {
33
33
  /// @notice Command that provisions assets to peer hosts from ALLOCATION request blocks.
34
34
  /// Each request block supplies the target host plus an asset amount; the output is a CUSTODY state stream.
35
35
  abstract contract Provision is CommandBase, ProvisionHook {
36
- uint internal immutable provisionId = commandId(this.provision.selector);
36
+ bytes32 private immutable descriptor;
37
37
 
38
38
  constructor() {
39
- emit Command(host, provisionId, "1:0:1", Schemas.Allocation, Keys.Empty, Keys.Custody, false);
40
- emit Labeled(provisionId, bytes32(0), "provision");
39
+ (, descriptor) = command("provision", Keys.Empty, Keys.Allocation, Keys.Custody, 0, false, false);
41
40
  }
42
41
 
43
42
  /// @notice Provision ALLOCATION request blocks and output matching CUSTODY state blocks.
44
- /// @param c Command context; `c.request` must contain ALLOCATION blocks.
43
+ /// @param c Command context; `c.input` must contain ALLOCATION blocks.
45
44
  /// @return CUSTODY block stream matching the provisioned allocations.
46
45
  function provision(CommandContext calldata c) external onlyCommand returns (bytes memory) {
47
- (Cur memory request, uint groups, ) = Cursors.init(c.request, 1);
48
- Writer memory writer = Writers.allocCustodies(groups);
46
+ (Cur memory input, uint outputs) = openInput(c.input, descriptor);
47
+ Writer memory output = Writers.allocCustodies(outputs);
49
48
 
50
- while (request.i < request.len) {
51
- HostAmount memory allocation = request.unpackAllocationValue();
49
+ while (input.i < input.len) {
50
+ HostAmount memory allocation = input.unpackAllocationValue();
52
51
  provision(c.account, allocation);
53
- writer.appendCustody(allocation);
52
+ output.appendCustody(allocation);
54
53
  }
55
54
 
56
- request.complete();
57
- return writer.finish();
55
+ return output.finish();
58
56
  }
59
57
  }
60
58
 
@@ -63,32 +61,30 @@ abstract contract Provision is CommandBase, ProvisionHook {
63
61
  /// Each request block supplies the target host plus an asset amount; the output is a CUSTODY state stream.
64
62
  /// The hook receives a mutable native-value budget drawn from `msg.value`.
65
63
  abstract contract ProvisionPayable is CommandBase, Payable, ProvisionPayableHook {
66
- uint internal immutable provisionPayableId = commandId(this.provisionPayable.selector);
64
+ bytes32 private immutable descriptor;
67
65
 
68
66
  constructor() {
69
- emit Command(host, provisionPayableId, "1:0:1", Schemas.Allocation, Keys.Empty, Keys.Custody, true);
70
- emit Labeled(provisionPayableId, bytes32(0), "provisionPayable");
67
+ (, descriptor) = command("provisionPayable", Keys.Empty, Keys.Allocation, Keys.Custody, 0, true, false);
71
68
  }
72
69
 
73
70
  /// @notice Provision ALLOCATION request blocks with access to a mutable native-value budget.
74
- /// @param c Command context; `c.request` must contain ALLOCATION blocks.
71
+ /// @param c Command context; `c.input` must contain ALLOCATION blocks.
75
72
  /// @return CUSTODY block stream matching the provisioned allocations.
76
73
  function provisionPayable(
77
74
  CommandContext calldata c
78
75
  ) external payable onlyCommand returns (bytes memory) {
79
- (Cur memory request, uint groups, ) = Cursors.init(c.request, 1);
80
- Writer memory writer = Writers.allocCustodies(groups);
76
+ (Cur memory input, uint outputs) = openInput(c.input, descriptor);
77
+ Writer memory output = Writers.allocCustodies(outputs);
81
78
  Budget memory budget = openValue();
82
79
 
83
- while (request.i < request.len) {
84
- HostAmount memory allocation = request.unpackAllocationValue();
80
+ while (input.i < input.len) {
81
+ HostAmount memory allocation = input.unpackAllocationValue();
85
82
  provision(c.account, allocation, budget);
86
- writer.appendCustody(allocation);
83
+ output.appendCustody(allocation);
87
84
  }
88
85
 
89
86
  closeValue(c.account, budget);
90
- request.complete();
91
- return writer.finish();
87
+ return output.finish();
92
88
  }
93
89
  }
94
90
 
@@ -3,39 +3,45 @@ pragma solidity ^0.8.33;
3
3
 
4
4
  import {CommandBase, CommandContext, Keys} from "./Base.sol";
5
5
  import {Payable} from "../core/Payable.sol";
6
- import {RecoverHook} from "../core/Portal.sol";
7
- import {Cursors, Cur, Schemas} from "../Cursors.sol";
6
+ import {Cursors, Cur} from "../Cursors.sol";
8
7
  import {Budget} from "../utils/Value.sol";
9
8
 
10
9
  using Cursors for Cur;
11
10
 
11
+ abstract contract RecoverHook {
12
+ /// @notice Override to recover a witness through `handler`.
13
+ /// @param handler Port that should attempt recovery.
14
+ /// @param key Recovery lookup key.
15
+ /// @param witness Witness payload used to prove and replay recovery.
16
+ /// @param value Native EVM value assigned to the recovery attempt.
17
+ function recover(uint handler, bytes32 key, bytes calldata witness, uint128 value) internal virtual;
18
+ }
19
+
12
20
  /// @title RecoverPayable
13
21
  /// @notice Command that forwards recover request blocks to a virtual hook.
14
22
  /// Recovery is witness-driven: the command account pays and receives leftover
15
23
  /// value settlement, but the recovered subject is defined by each witness.
16
24
  /// Produces no output state.
17
25
  abstract contract RecoverPayable is CommandBase, Payable, RecoverHook {
18
- uint internal immutable recoverPayableId = commandId(this.recoverPayable.selector);
26
+ bytes32 private immutable descriptor;
19
27
 
20
28
  constructor() {
21
- emit Command(host, recoverPayableId, "1:0:0", Schemas.Recover, Keys.Empty, Keys.Empty, true);
22
- emit Labeled(recoverPayableId, bytes32(0), "recoverPayable");
29
+ (, descriptor) = command("recoverPayable", Keys.Empty, Keys.Recover, Keys.Empty, 0, true, false);
23
30
  }
24
31
 
25
32
  /// @notice Recover each recover block in the command request.
26
- /// @param c Command context; `c.request` must contain Recover blocks.
33
+ /// @param c Command context; `c.input` must contain Recover blocks.
27
34
  /// @return Empty output state.
28
35
  function recoverPayable(CommandContext calldata c) external payable onlyCommand returns (bytes memory) {
29
- (Cur memory request, , ) = Cursors.init(c.request, 1);
36
+ (Cur memory input, ) = openInput(c.input, descriptor);
30
37
  Budget memory budget = openValue();
31
38
 
32
- while (request.i < request.len) {
33
- (uint handler, uint resources, bytes32 key, bytes calldata witness) = request.unpackRecover();
39
+ while (input.i < input.len) {
40
+ (uint handler, uint resources, bytes32 key, bytes calldata witness) = input.unpackRecover();
34
41
  recover(handler, key, witness, useValue(budget, resources));
35
42
  }
36
43
 
37
44
  closeValue(c.account, budget);
38
- request.complete();
39
45
  return "";
40
46
  }
41
47
  }