@rootzero/contracts 1.12.0 → 1.13.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.
@@ -42,7 +42,8 @@ abstract contract Provision is CommandBase, ProvisionHook {
42
42
  /// @notice Provision ALLOCATION request blocks and output matching CUSTODY state blocks.
43
43
  /// @param c Command context; `c.input` must contain ALLOCATION blocks.
44
44
  /// @return CUSTODY block stream matching the provisioned allocations.
45
- function provision(CommandContext calldata c) external onlyCommand returns (bytes memory) {
45
+ /// @return Empty transaction stream.
46
+ function provision(CommandContext calldata c) external onlyCommand returns (bytes memory, bytes memory) {
46
47
  (Cur memory input, uint outputs) = openInput(c.input, descriptor);
47
48
  Writer memory output = Writers.allocCustodies(outputs);
48
49
 
@@ -52,7 +53,7 @@ abstract contract Provision is CommandBase, ProvisionHook {
52
53
  output.appendCustody(allocation);
53
54
  }
54
55
 
55
- return output.finish();
56
+ return (output.finish(), "");
56
57
  }
57
58
  }
58
59
 
@@ -70,9 +71,10 @@ abstract contract ProvisionPayable is CommandBase, Payable, ProvisionPayableHook
70
71
  /// @notice Provision ALLOCATION request blocks with access to a mutable native-value budget.
71
72
  /// @param c Command context; `c.input` must contain ALLOCATION blocks.
72
73
  /// @return CUSTODY block stream matching the provisioned allocations.
74
+ /// @return Remaining native value as a refund transaction stream.
73
75
  function provisionPayable(
74
76
  CommandContext calldata c
75
- ) external payable onlyCommand returns (bytes memory) {
77
+ ) external payable onlyCommand returns (bytes memory, bytes memory) {
76
78
  (Cur memory input, uint outputs) = openInput(c.input, descriptor);
77
79
  Writer memory output = Writers.allocCustodies(outputs);
78
80
  Budget memory budget = openValue();
@@ -83,8 +85,7 @@ abstract contract ProvisionPayable is CommandBase, Payable, ProvisionPayableHook
83
85
  output.appendCustody(allocation);
84
86
  }
85
87
 
86
- closeValue(c.account, budget);
87
- return output.finish();
88
+ return (output.finish(), closeValue(budget, c.account));
88
89
  }
89
90
  }
90
91
 
@@ -32,7 +32,8 @@ abstract contract RecoverPayable is CommandBase, Payable, RecoverHook {
32
32
  /// @notice Recover each recover block in the command request.
33
33
  /// @param c Command context; `c.input` must contain Recover blocks.
34
34
  /// @return Empty output state.
35
- function recoverPayable(CommandContext calldata c) external payable onlyCommand returns (bytes memory) {
35
+ /// @return Remaining native value as a refund transaction stream.
36
+ function recoverPayable(CommandContext calldata c) external payable onlyCommand returns (bytes memory, bytes memory) {
36
37
  (Cur memory input, ) = openInput(c.input, descriptor);
37
38
  Budget memory budget = openValue();
38
39
 
@@ -41,7 +42,6 @@ abstract contract RecoverPayable is CommandBase, Payable, RecoverHook {
41
42
  recover(handler, key, witness, useValue(budget, resources));
42
43
  }
43
44
 
44
- closeValue(c.account, budget);
45
- return "";
45
+ return ("", closeValue(budget, c.account));
46
46
  }
47
47
  }
@@ -35,14 +35,14 @@ abstract contract RelayPayable is CommandBase, Payable, RoutePayableHook {
35
35
  /// @notice Relay one RELAY request block with the command account and current state.
36
36
  /// @param c Command context; `c.input` must contain exactly one RELAY block.
37
37
  /// @return Empty output state.
38
- function relayPayable(CommandContext calldata c) external payable onlyCommand returns (bytes memory) {
38
+ /// @return Remaining native value as a refund transaction stream.
39
+ function relayPayable(CommandContext calldata c) external payable onlyCommand returns (bytes memory, bytes memory) {
39
40
  (Cur memory input, , ) = openLane(c.input, descriptor, Lane.Input, 1);
40
41
  (uint portal, uint resources, bytes memory context) = input.relayToContext(c.account, c.state);
41
42
 
42
43
  Budget memory budget = openValue();
43
44
  route(portal, resources, context, budget);
44
45
 
45
- closeValue(c.account, budget);
46
- return "";
46
+ return ("", closeValue(budget, c.account));
47
47
  }
48
48
  }
@@ -28,16 +28,17 @@ abstract contract Withdraw is CommandBase, WithdrawHook {
28
28
  /// @notice Withdraw each BALANCE block from the command state to the command account.
29
29
  /// @param c Command context; `c.state` must contain BALANCE blocks.
30
30
  /// @return Empty output state.
31
+ /// @return Empty transaction stream.
31
32
  function withdraw(
32
33
  CommandContext calldata c
33
- ) external onlyCommand returns (bytes memory) {
34
+ ) external onlyCommand returns (bytes memory, bytes memory) {
34
35
  (Cur memory state, ) = openState(c.state, descriptor);
35
36
 
36
37
  while (state.i < state.len) {
37
38
  (bytes32 asset, uint amount) = state.unpackBalance();
38
39
  withdraw(c.account, asset, amount);
39
40
  }
40
- return "";
41
+ return ("", "");
41
42
  }
42
43
  }
43
44
 
@@ -25,16 +25,17 @@ abstract contract AllowAssets is AdminBase, AllowAssetsHook {
25
25
  /// @notice Allow each ASSET block in the admin request.
26
26
  /// @param c Admin command context; `c.input` must contain ASSET blocks.
27
27
  /// @return Empty output state.
28
+ /// @return Empty transaction stream.
28
29
  function allowAssets(
29
30
  CommandContext calldata c
30
- ) external onlyAdmin(c.account) returns (bytes memory) {
31
+ ) external onlyAdmin(c.account) returns (bytes memory, bytes memory) {
31
32
  (Cur memory input, ) = openInput(c.input, descriptor);
32
33
 
33
34
  while (input.i < input.len) {
34
35
  bytes32 asset = input.unpackAsset();
35
36
  allowAsset(asset);
36
37
  }
37
- return "";
38
+ return ("", "");
38
39
  }
39
40
  }
40
41
 
@@ -29,13 +29,14 @@ abstract contract Allowance is AdminBase, AllowanceHook {
29
29
  /// @notice Apply each ALLOWANCE block in the admin request.
30
30
  /// @param c Admin command context; `c.input` must contain ALLOWANCE blocks.
31
31
  /// @return Empty output state.
32
- function allowance(CommandContext calldata c) external onlyAdmin(c.account) returns (bytes memory) {
32
+ /// @return Empty transaction stream.
33
+ function allowance(CommandContext calldata c) external onlyAdmin(c.account) returns (bytes memory, bytes memory) {
33
34
  (Cur memory input, ) = openInput(c.input, descriptor);
34
35
 
35
36
  while (input.i < input.len) {
36
37
  (uint peer, bytes32 asset, uint amount) = input.unpackAllowance();
37
38
  allowance(peer, asset, amount);
38
39
  }
39
- return "";
40
+ return ("", "");
40
41
  }
41
42
  }
@@ -19,15 +19,16 @@ abstract contract Appoint is AdminBase {
19
19
  /// @notice Appoint each ACCOUNT block in the admin request as a guardian.
20
20
  /// @param c Admin command context; `c.input` must contain ACCOUNT blocks.
21
21
  /// @return Empty output state.
22
+ /// @return Empty transaction stream.
22
23
  function appoint(
23
24
  CommandContext calldata c
24
- ) external onlyAdmin(c.account) returns (bytes memory) {
25
+ ) external onlyAdmin(c.account) returns (bytes memory, bytes memory) {
25
26
  (Cur memory input, ) = openInput(c.input, descriptor);
26
27
 
27
28
  while (input.i < input.len) {
28
29
  bytes32 account = input.unpackAccount();
29
30
  setGuardian(account, true);
30
31
  }
31
- return "";
32
+ return ("", "");
32
33
  }
33
34
  }
@@ -20,13 +20,14 @@ abstract contract Authorize is AdminBase {
20
20
  /// @notice Authorize each NODE block in the admin request.
21
21
  /// @param c Admin command context; `c.input` must contain NODE blocks.
22
22
  /// @return Empty output state.
23
- function authorize(CommandContext calldata c) external onlyAdmin(c.account) returns (bytes memory) {
23
+ /// @return Empty transaction stream.
24
+ function authorize(CommandContext calldata c) external onlyAdmin(c.account) returns (bytes memory, bytes memory) {
24
25
  (Cur memory input, ) = openInput(c.input, descriptor);
25
26
 
26
27
  while (input.i < input.len) {
27
28
  uint node = input.unpackNode();
28
29
  setNode(node, true);
29
30
  }
30
- return "";
31
+ return ("", "");
31
32
  }
32
33
  }
@@ -25,16 +25,17 @@ abstract contract DenyAssets is AdminBase, DenyAssetsHook {
25
25
  /// @notice Deny each ASSET block in the admin request.
26
26
  /// @param c Admin command context; `c.input` must contain ASSET blocks.
27
27
  /// @return Empty output state.
28
+ /// @return Empty transaction stream.
28
29
  function denyAssets(
29
30
  CommandContext calldata c
30
- ) external onlyAdmin(c.account) returns (bytes memory) {
31
+ ) external onlyAdmin(c.account) returns (bytes memory, bytes memory) {
31
32
  (Cur memory input, ) = openInput(c.input, descriptor);
32
33
 
33
34
  while (input.i < input.len) {
34
35
  bytes32 asset = input.unpackAsset();
35
36
  denyAsset(asset);
36
37
  }
37
- return "";
38
+ return ("", "");
38
39
  }
39
40
  }
40
41
 
@@ -19,15 +19,16 @@ abstract contract Dismiss is AdminBase {
19
19
  /// @notice Dismiss each ACCOUNT block in the admin request from guardian status.
20
20
  /// @param c Admin command context; `c.input` must contain ACCOUNT blocks.
21
21
  /// @return Empty output state.
22
+ /// @return Empty transaction stream.
22
23
  function dismiss(
23
24
  CommandContext calldata c
24
- ) external onlyAdmin(c.account) returns (bytes memory) {
25
+ ) external onlyAdmin(c.account) returns (bytes memory, bytes memory) {
25
26
  (Cur memory input, ) = openInput(c.input, descriptor);
26
27
 
27
28
  while (input.i < input.len) {
28
29
  bytes32 account = input.unpackAccount();
29
30
  setGuardian(account, false);
30
31
  }
31
- return "";
32
+ return ("", "");
32
33
  }
33
34
  }
@@ -23,7 +23,8 @@ abstract contract ExecutePayable is AdminBase, Payable {
23
23
  /// @notice Execute each CALL block in the admin request.
24
24
  /// @param c Admin command context; `c.input` must contain CALL blocks.
25
25
  /// @return Empty output state.
26
- function executePayable(CommandContext calldata c) external payable onlyAdmin(c.account) returns (bytes memory) {
26
+ /// @return Empty transaction stream.
27
+ function executePayable(CommandContext calldata c) external payable onlyAdmin(c.account) returns (bytes memory, bytes memory) {
27
28
  (Cur memory input, ) = openInput(c.input, descriptor);
28
29
  Budget memory budget = openValue();
29
30
 
@@ -31,6 +32,6 @@ abstract contract ExecutePayable is AdminBase, Payable {
31
32
  (uint target, uint resources, bytes calldata data) = input.unpackCall();
32
33
  rawCall(target, useValue(budget, resources), data);
33
34
  }
34
- return "";
35
+ return ("", "");
35
36
  }
36
37
  }
@@ -19,13 +19,14 @@ abstract contract Label is AdminBase {
19
19
  /// @notice Publish each LABEL block in the admin request.
20
20
  /// @param c Admin command context; `c.input` must contain LABEL blocks.
21
21
  /// @return Empty output state.
22
- function label(CommandContext calldata c) external onlyAdmin(c.account) returns (bytes memory) {
22
+ /// @return Empty transaction stream.
23
+ function label(CommandContext calldata c) external onlyAdmin(c.account) returns (bytes memory, bytes memory) {
23
24
  (Cur memory input, ) = openInput(c.input, descriptor);
24
25
 
25
26
  while (input.i < input.len) {
26
27
  (uint node, bytes32 namespace, string memory name) = input.unpackLabel();
27
28
  emit Labeled(node, namespace, name);
28
29
  }
29
- return "";
30
+ return ("", "");
30
31
  }
31
32
  }
@@ -19,13 +19,14 @@ abstract contract PublishSchema is AdminBase {
19
19
  /// @notice Publish each SCHEMA block in the admin request.
20
20
  /// @param c Admin command context; `c.input` must contain SCHEMA blocks.
21
21
  /// @return Empty output state.
22
- function publishSchema(CommandContext calldata c) external onlyAdmin(c.account) returns (bytes memory) {
22
+ /// @return Empty transaction stream.
23
+ function publishSchema(CommandContext calldata c) external onlyAdmin(c.account) returns (bytes memory, bytes memory) {
23
24
  (Cur memory input, ) = openInput(c.input, descriptor);
24
25
 
25
26
  while (input.i < input.len) {
26
27
  (bytes4 key, string memory body, bytes32 name) = input.unpackSchema();
27
28
  emit Schema(host, key, body, name);
28
29
  }
29
- return "";
30
+ return ("", "");
30
31
  }
31
32
  }
@@ -20,13 +20,14 @@ abstract contract Unauthorize is AdminBase {
20
20
  /// @notice Unauthorize each NODE block in the admin request.
21
21
  /// @param c Admin command context; `c.input` must contain NODE blocks.
22
22
  /// @return Empty output state.
23
- function unauthorize(CommandContext calldata c) external onlyAdmin(c.account) returns (bytes memory) {
23
+ /// @return Empty transaction stream.
24
+ function unauthorize(CommandContext calldata c) external onlyAdmin(c.account) returns (bytes memory, bytes memory) {
24
25
  (Cur memory input, ) = openInput(c.input, descriptor);
25
26
 
26
27
  while (input.i < input.len) {
27
28
  uint node = input.unpackNode();
28
29
  setNode(node, false);
29
30
  }
30
- return "";
31
+ return ("", "");
31
32
  }
32
33
  }
package/core/Calls.sol CHANGED
@@ -90,17 +90,18 @@ abstract contract CommandCalls is NodeCalls {
90
90
  /// @param account Command account identifier.
91
91
  /// @param state Current command state block stream.
92
92
  /// @param request Command input block stream.
93
- /// @return Decoded command output block stream.
93
+ /// @return nextState Decoded command output state block stream.
94
+ /// @return transactions Decoded command transaction block stream.
94
95
  function callCommand(
95
96
  uint command,
96
97
  uint128 value,
97
98
  bytes32 account,
98
99
  bytes memory state,
99
100
  bytes calldata request
100
- ) internal returns (bytes memory) {
101
+ ) internal returns (bytes memory nextState, bytes memory transactions) {
101
102
  bytes4 selector = Nodes.commandSelector(command);
102
103
  bytes memory data = abi.encodeWithSelector(selector, CommandContext(account, state, request));
103
- return abi.decode(trustedCall(command, value, data), (bytes));
104
+ return abi.decode(trustedCall(command, value, data), (bytes, bytes));
104
105
  }
105
106
  }
106
107
 
package/core/Endpoint.sol CHANGED
@@ -58,21 +58,6 @@ abstract contract EndpointBase is Runtime, EndpointEvent, LabeledEvent, SchemaEv
58
58
  if (size == 0) size = 1;
59
59
  }
60
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
61
  /// @dev Open a descriptor lane and return its effective group and output counts.
77
62
  /// An absent lane inherits `expected`; a present lane must match it when nonzero.
78
63
  /// @param source Block stream to open for the requested lane.
@@ -118,20 +103,37 @@ abstract contract EndpointBase is Runtime, EndpointEvent, LabeledEvent, SchemaEv
118
103
  (input, , outputs) = openLane(source, descriptor, Lane.Input, 0);
119
104
  }
120
105
 
121
- /// @notice Publish the default local block schema and return `Keys.Local`.
122
- /// @param body Schema DSL string describing the block payload body.
123
- /// @return The default context-local block key.
124
- function localSchema(string memory body) internal returns (bytes4) {
125
- return localSchema(1, body);
106
+ /// @notice Return an 8-byte lane value for a generic LIST containing `item`.
107
+ /// @param item Block key expected inside each LIST payload.
108
+ /// @return Packed lane key `[Keys.List][item]`.
109
+ function many(bytes4 item) internal pure returns (bytes8) {
110
+ return bytes8(bytes.concat(Keys.List, item));
111
+ }
112
+
113
+ /// @notice Append an explicit group size to an 8-byte lane value.
114
+ /// @param value Packed lane key `[key][item]`.
115
+ /// @param size Explicit per-operation group size for the lane.
116
+ /// @return Packed lane key plus group byte.
117
+ function group(bytes8 value, uint8 size) internal pure returns (bytes9) {
118
+ return bytes9(bytes.concat(value, bytes1(size)));
126
119
  }
127
120
 
128
121
  /// @notice Publish a context-local block schema and return its key.
129
122
  /// @param key Context-local key value.
130
123
  /// @param body Schema DSL string describing the block payload body.
131
124
  /// @return The context-local block key.
132
- function localSchema(uint32 key, string memory body) internal returns (bytes4) {
133
- bytes4 k = Keys.local(key);
134
- emit Schema(host, k, body, bytes32(0));
125
+ function schema(uint32 key, string memory body) internal returns (bytes4) {
126
+ return schema(key, body, bytes32(0));
127
+ }
128
+
129
+ /// @notice Publish a named context-local block schema and return its key.
130
+ /// @param key Context-local key value.
131
+ /// @param body Schema DSL string describing the block payload body.
132
+ /// @param name Schema alias name, or zero for unnamed schemas.
133
+ /// @return The context-local block key.
134
+ function schema(uint32 key, string memory body, bytes32 name) internal returns (bytes4) {
135
+ bytes4 k = bytes4(key);
136
+ emit Schema(host, k, body, name);
135
137
  return k;
136
138
  }
137
139
 
package/core/Host.sol CHANGED
@@ -43,7 +43,15 @@ abstract contract Host is
43
43
  /// on it during construction.
44
44
  constructor(address cmdr) AccessControl(cmdr) {
45
45
  if (cmdr == address(0) || cmdr == address(this) || cmdr.code.length == 0) return;
46
- IHostIntroduction(cmdr).introduce(host, block.number);
46
+ introduceTo(Nodes.toHost(cmdr));
47
+ }
48
+
49
+ /// @notice Introduce this host to the contract address embedded in a local EVM node ID.
50
+ /// @dev Accepts host and endpoint IDs such as commands, ports, queries, and guards.
51
+ /// Reverts when `node` is not local or embeds the zero address.
52
+ /// @param node Local EVM node ID whose underlying contract receives the introduction.
53
+ function introduceTo(uint node) internal {
54
+ IHostIntroduction(Nodes.addr(node)).introduce(host, block.number);
47
55
  }
48
56
 
49
57
  /// @notice Record a host introduction claim.
@@ -51,7 +59,7 @@ abstract contract Host is
51
59
  /// @param peer Host node ID being introduced.
52
60
  /// @param blocknum Block number at which the host was deployed.
53
61
  function introduce(uint peer, uint blocknum) external {
54
- emit Introduction(Nodes.matchHost(peer, msg.sender), blocknum);
62
+ emit Introduction(host, Nodes.matchHost(peer, msg.sender), blocknum);
55
63
  }
56
64
 
57
65
  /// @notice Accept native ETH transfers (e.g. from command value flows).
package/core/Payable.sol CHANGED
@@ -2,15 +2,16 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import {Budget, Values} from "../utils/Value.sol";
5
+ import {Cursors} from "../Cursors.sol";
6
+ import {ReceivedEvent} from "../events/Received.sol";
7
+ import {Actions} from "../utils/Actions.sol";
8
+ import {NativeAsset} from "./Runtime.sol";
5
9
  import {max128} from "../utils/Utils.sol";
6
10
 
7
11
  /// @title Payable
8
12
  /// @notice Abstract mixin for entrypoints that accept native value (`msg.value`).
9
13
  /// Provides shared helpers for mutable native-value budgets.
10
- abstract contract Payable {
11
- /// @dev Thrown when a payable entrypoint completes with unspent native value.
12
- error UnusedValue(uint remaining);
13
-
14
+ abstract contract Payable is NativeAsset, ReceivedEvent {
14
15
  /// @notice Open a native-value budget from the current call's `msg.value`.
15
16
  /// @return Budget initialized with the full `msg.value`.
16
17
  function openValue() internal view returns (Budget memory) {
@@ -33,21 +34,20 @@ abstract contract Payable {
33
34
  Values.use(budget, value);
34
35
  }
35
36
 
36
- /// @notice Close a native-value budget and settle any drained value.
37
- /// @param account Account identifier for the current invocation.
38
- /// @param budget Mutable native-value budget to close.
39
- function closeValue(bytes32 account, Budget memory budget) internal {
40
- uint value = Values.drain(budget);
41
- if (value == 0) return;
42
- settleValue(account, value);
43
- }
37
+ /// @notice Drain a native-value budget into a credit-only TRANSACTION block.
38
+ /// @dev Emits `Received` with `Actions.Refund` when a transaction is created.
39
+ /// @param budget Mutable budget whose remaining value is drained.
40
+ /// @param account Destination account to credit with the remaining native value.
41
+ /// @return transaction Encoded TRANSACTION block, or empty bytes when the budget is empty.
42
+ function closeValue(
43
+ Budget memory budget,
44
+ bytes32 account
45
+ ) internal returns (bytes memory transaction) {
46
+ uint amount = Values.drain(budget);
47
+ if (amount == 0) return "";
44
48
 
45
- /// @notice Handle a drained native value amount.
46
- /// @dev Override to refund or redirect unused value. The default rejects it.
47
- /// @param account Account identifier for the current invocation.
48
- /// @param value Drained native value amount to settle, in wei.
49
- function settleValue(bytes32 account, uint value) internal virtual {
50
- account;
51
- revert UnusedValue(value);
49
+ transaction = Cursors.toTransactionBlock(bytes32(0), account, nativeAsset, amount);
50
+ emit Received(account, nativeAsset, amount, Actions.Refund, 0);
52
51
  }
52
+
53
53
  }
package/core/Pipeline.sol CHANGED
@@ -1,34 +1,38 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import {Cursors, Cur} from "../Cursors.sol";
4
+ import {Cursors, Cur, Readers, Reader} from "../Cursors.sol";
5
5
  import {Payable} from "./Payable.sol";
6
+ import {Settlement} from "./Settlement.sol";
6
7
  import {Budget} from "../utils/Value.sol";
7
8
 
8
9
  using Cursors for Cur;
10
+ using Readers for Reader;
9
11
 
10
12
  /// @title Pipeline
11
13
  /// @notice Core pipeline functionality shared by higher-level surfaces.
12
- abstract contract Pipeline is Payable {
14
+ abstract contract Pipeline is Payable, Settlement {
13
15
  /// @dev Thrown when the pipeline finishes with non-empty threaded state.
14
16
  error UnexpectedState();
15
17
 
16
18
  /// @notice Override to dispatch one piped step.
17
- /// Called once per STEP block. The returned bytes become the state passed to
18
- /// the next step, and the final returned state must be empty.
19
+ /// Called once per STEP block. The returned state becomes the state passed to
20
+ /// the next step, and the final returned state must be empty. Returned
21
+ /// transactions are decoded and passed individually to `settle` before the next step runs.
19
22
  /// @param target Node ID to invoke or handle.
20
23
  /// @param account Account identifier for the piped context.
21
24
  /// @param state Current threaded state block stream.
22
25
  /// @param request Step request block stream.
23
26
  /// @param value Native EVM value assigned to this step.
24
- /// @return Updated state block stream for the next step.
27
+ /// @return nextState Updated state block stream for the next step.
28
+ /// @return transactions Transaction block stream produced by the command.
25
29
  function dispatch(
26
30
  uint target,
27
31
  bytes32 account,
28
32
  bytes memory state,
29
33
  bytes calldata request,
30
34
  uint128 value
31
- ) internal virtual returns (bytes memory);
35
+ ) internal virtual returns (bytes memory nextState, bytes memory transactions);
32
36
 
33
37
  /// @notice Execute a STEP block stream through the pipeline.
34
38
  /// @dev Reverts with `UnexpectedState` if the final threaded state is non-empty.
@@ -37,20 +41,19 @@ abstract contract Pipeline is Payable {
37
41
  /// @param state Initial state block stream passed to the first step.
38
42
  /// @param steps STEP block stream to execute.
39
43
  /// @param budget Mutable native-value budget shared across all steps.
40
- function pipe(
41
- bytes32 account,
42
- bytes memory state,
43
- bytes calldata steps,
44
- Budget memory budget
45
- ) internal {
44
+ function pipe(bytes32 account, bytes memory state, bytes calldata steps, Budget memory budget) internal {
46
45
  (Cur memory input, ) = Cursors.init(steps, 1);
47
46
 
48
47
  while (input.i < input.len) {
49
48
  (uint target, uint resources, bytes calldata request) = input.unpackStep();
50
- state = dispatch(target, account, state, request, useValue(budget, resources));
49
+ Reader memory txs;
50
+ (state, txs.source) = dispatch(target, account, state, request, useValue(budget, resources));
51
+ while (txs.more()) {
52
+ (bytes32 from, bytes32 to, bytes32 asset, uint amount) = txs.unpackTransaction();
53
+ settle(from, to, asset, amount);
54
+ }
51
55
  }
52
56
 
53
57
  if (state.length != 0) revert UnexpectedState();
54
- input.complete();
55
58
  }
56
59
  }
@@ -0,0 +1,39 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ /// @title DebitAccountHook
5
+ /// @notice Hook for debiting externally managed account funds.
6
+ abstract contract DebitAccountHook {
7
+ /// @notice Override to debit externally managed funds from `account`.
8
+ /// @param account Source account identifier.
9
+ /// @param asset Asset identifier.
10
+ /// @param amount Amount to debit.
11
+ function debitAccount(bytes32 account, bytes32 asset, uint amount) internal virtual;
12
+ }
13
+
14
+ /// @title CreditAccountHook
15
+ /// @notice Hook for crediting externally managed account funds.
16
+ abstract contract CreditAccountHook {
17
+ /// @notice Override to credit externally managed funds to `account`.
18
+ /// @param account Destination account identifier.
19
+ /// @param asset Asset identifier.
20
+ /// @param amount Amount to credit.
21
+ function creditAccount(bytes32 account, bytes32 asset, uint amount) internal virtual;
22
+ }
23
+
24
+ /// @title Settlement
25
+ /// @notice Settles decoded transactions through debit and credit account hooks.
26
+ abstract contract Settlement is DebitAccountHook, CreditAccountHook {
27
+ /// @notice Settle one transaction by debiting its source and crediting its destination.
28
+ /// Returns without calling either hook when `amount` is zero and skips either
29
+ /// operation when the corresponding account is zero.
30
+ /// @param from Source account identifier.
31
+ /// @param to Destination account identifier.
32
+ /// @param asset Asset identifier.
33
+ /// @param amount Token amount.
34
+ function settle(bytes32 from, bytes32 to, bytes32 asset, uint amount) internal {
35
+ if (amount == 0) return;
36
+ if (from != 0) debitAccount(from, asset, amount);
37
+ if (to != 0) creditAccount(to, asset, amount);
38
+ }
39
+ }
package/docs/Schema.md CHANGED
@@ -33,7 +33,7 @@ used. A host can publish the meaning of a custom key with:
33
33
  event Schema(uint indexed host, bytes4 key, string schema, bytes32 name);
34
34
  ```
35
35
 
36
- For example, a host-specific payment block can use `Keys.Local`, the command
36
+ For example, a host-specific payment block can use a small literal, the command
37
37
  selector, or any other chosen `bytes4` value as long as that key is not
38
38
  overloaded in the relevant host/schema context.
39
39
 
@@ -300,17 +300,17 @@ invalid in any path segment.
300
300
  - `#list`: generic list wrapper emitted by `many`
301
301
 
302
302
  Custom input shapes should define their own context-local block key and publish
303
- that key with a `Schema` event:
303
+ that key with a `Schema` event. Endpoint contracts can use `schema(...)` for
304
+ that publication:
304
305
 
305
306
  ```solidity
306
- bytes4 constant Input = Keys.Local;
307
- emit Schema(host, Input, "{ bytes32 asset, uint amount }", bytes32("payment"));
307
+ bytes4 input = schema(1, "{ bytes32 asset, uint amount }");
308
308
  ```
309
309
 
310
- Use `Keys.local(n)` when a host needs more than one local block key. The key can
311
- also be a small literal, a selector, or any other `bytes4` value that is unique
312
- in the context where it is used. The alias names the block; the schema string
313
- describes only the payload body.
310
+ Use different numeric keys when a host needs more than one local block key. The
311
+ key can also be a selector or any other `bytes4` value that is unique in the
312
+ context where it is used. The alias names the block; the schema string describes
313
+ only the payload body.
314
314
 
315
315
  ## Standard Blocks
316
316
 
@@ -5,11 +5,12 @@ import { EventEmitter } from "./Emitter.sol";
5
5
 
6
6
  /// @notice Emitted when a host introduces itself to another host.
7
7
  abstract contract IntroductionEvent is EventEmitter {
8
- string private constant ABI = "event Introduction(uint indexed host, uint blocknum)";
8
+ string private constant ABI = "event Introduction(uint indexed host, uint peer, uint blocknum)";
9
9
 
10
- /// @param host Host node ID of the introducing contract.
10
+ /// @param host Host node ID receiving the introduction.
11
+ /// @param peer Host node ID of the introducing contract.
11
12
  /// @param blocknum Block number at which the host was deployed.
12
- event Introduction(uint indexed host, uint blocknum);
13
+ event Introduction(uint indexed host, uint peer, uint blocknum);
13
14
 
14
15
  constructor() {
15
16
  emit EventAbi(ABI);