@rootzero/contracts 1.6.0 → 1.7.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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,39 @@
3
3
  Until the protocol reaches integration-stable status, minor versions may include
4
4
  breaking API changes. Breaking changes are called out explicitly.
5
5
 
6
+ ## 1.7.0
7
+
8
+ ### Breaking Changes
9
+
10
+ - Renamed peer callable surfaces to ports:
11
+ - `contracts/peer` moved to `contracts/ports`.
12
+ - `PeerEvent` / `event Peer` became `PortEvent` / `event Port`.
13
+ - `PeerBase`, `IPeer*`, and `Peer*` endpoint contracts became `PortBase`,
14
+ `IPort*`, and `Port*`.
15
+ - Peer endpoint functions and labels now use `port*` names, such as
16
+ `portPipePayable`, `portDispatchPayable`, and `portSettle`.
17
+ - Node helpers and layout tags now use `Port` terminology:
18
+ `Nodes.toPort`, `Nodes.isPort`, `Nodes.port`, and
19
+ `Nodes.portSelector`.
20
+ - Removed the generic `PortRecoverContextPayable`; recovery is now routed by
21
+ the command-level `recoverContextPayable` to concrete ports such as
22
+ `portPipePayable`.
23
+ - Renamed the `#contextRecovery` handler field from `target` to `port`.
24
+ - Changed port entrypoint calldata parameter naming to `data` to avoid
25
+ clashing with nested context `request` fields.
26
+
27
+ ### Added
28
+
29
+ - Added `RecoverContextPayable` and `#contextRecovery` for command-level
30
+ recovery routing with a commitment key, resources, handler port, and context
31
+ witness.
32
+ - Added `Dispatch(uint indexed host, uint chain, uint resources, bytes32 digest, bytes32 ref)`
33
+ as the discovery/event surface for dispatch tracking.
34
+ - Added `ContextRecovery` schema/cursor support and context schema aliases for
35
+ reusable nested block schemas.
36
+ - Added `Values.drain`, `Payable.openValue`, and `Payable.closeValue` to make
37
+ payable command budget lifecycles explicit.
38
+
6
39
  ## 1.6.0
7
40
 
8
41
  ### Added
@@ -15,7 +48,7 @@ breaking API changes. Breaking changes are called out explicitly.
15
48
  asset ID without the full host runtime.
16
49
  - Added `Escrows` as a keyed ledger for amounts reserved outside normal
17
50
  balances.
18
- - Added `Commitment(bytes32 indexed account, bytes32 key, bytes32 digest, uint status)`
51
+ - Added `Commitment(uint indexed host, bytes32 key, bytes32 digest, uint status)`
19
52
  and the `Commitments` core mixin for digest commitments and witness/recovery
20
53
  flows.
21
54
 
package/Endpoints.sol CHANGED
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- // Aggregator: re-exports command, admin, peer, guard, and query endpoint abstractions.
4
+ // Aggregator: re-exports command, admin, port, guard, and query endpoint abstractions.
5
5
  // Import this file to inherit from the full rootzero callable host surface without managing individual paths.
6
6
 
7
7
  // Shared helpers
@@ -16,6 +16,7 @@ import { DebitAccount, DebitAccountHook } from "./commands/Debit.sol";
16
16
  import { Deposit, DepositHook, DepositPayable, DepositPayableHook } from "./commands/Deposit.sol";
17
17
  import { Payout, PayoutHook } from "./commands/Payout.sol";
18
18
  import { Provision, ProvisionHook, ProvisionPayable, ProvisionPayableHook } from "./commands/Provision.sol";
19
+ import { RecoverContextPayable, RecoverContextPayableHook } from "./commands/Recover.sol";
19
20
  import { RelayPayable, DispatchPayableHook } from "./commands/Relay.sol";
20
21
  import { Withdraw, WithdrawHook } from "./commands/Withdraw.sol";
21
22
 
@@ -33,18 +34,17 @@ import { Init, InitHook } from "./commands/admin/Init.sol";
33
34
  import { Label } from "./commands/admin/Label.sol";
34
35
  import { Unauthorize } from "./commands/admin/Unauthorize.sol";
35
36
 
36
- // Peer endpoints
37
- import { PeerBase, encodePeerCall } from "./peer/Base.sol";
38
- import { PeerAllowAssets, IPeerAllowAssets } from "./peer/AllowAssets.sol";
39
- import { PeerAllowance, IPeerAllowance } from "./peer/Allowance.sol";
40
- import { PeerRedeemBalance, RedeemBalanceHook, IPeerRedeemBalance } from "./peer/Redeem.sol";
41
- import { PeerCreditAccount, IPeerCreditAccount } from "./peer/Credit.sol";
42
- import { PeerDebitAccount, IPeerDebitAccount } from "./peer/Debit.sol";
43
- import { PeerDenyAssets, IPeerDenyAssets } from "./peer/DenyAssets.sol";
44
- import { PeerPipePayable, IPeerPipePayable } from "./peer/Pipe.sol";
45
- import { PeerRecover, RecoverHook, IPeerRecover } from "./peer/Recover.sol";
46
- import { PeerDispatchPayable, IPeerDispatchPayable } from "./peer/Dispatch.sol";
47
- import { PeerSettle, IPeerSettle } from "./peer/Settle.sol";
37
+ // Port endpoints
38
+ import { PortBase, encodePortCall } from "./ports/Base.sol";
39
+ import { PortAllowAssets, IPortAllowAssets } from "./ports/AllowAssets.sol";
40
+ import { PortAllowance, IPortAllowance } from "./ports/Allowance.sol";
41
+ import { PortRedeemBalance, RedeemBalanceHook, IPortRedeemBalance } from "./ports/Redeem.sol";
42
+ import { PortCreditAccount, IPortCreditAccount } from "./ports/Credit.sol";
43
+ import { PortDebitAccount, IPortDebitAccount } from "./ports/Debit.sol";
44
+ import { PortDenyAssets, IPortDenyAssets } from "./ports/DenyAssets.sol";
45
+ import { PortPipePayable, IPortPipePayable } from "./ports/Pipe.sol";
46
+ import { PortDispatchPayable, IPortDispatchPayable } from "./ports/Dispatch.sol";
47
+ import { PortSettle, IPortSettle } from "./ports/Settle.sol";
48
48
 
49
49
  // Guard endpoints
50
50
  import { GuardBase, encodeGuardCall } from "./guards/Base.sol";
package/Events.sol CHANGED
@@ -11,6 +11,7 @@ import { BalanceEvent } from "./events/Balance.sol";
11
11
  import { CommanderEvent } from "./events/Commander.sol";
12
12
  import { CommandEvent } from "./events/Command.sol";
13
13
  import { CommitmentEvent } from "./events/Commitment.sol";
14
+ import { DispatchEvent } from "./events/Dispatch.sol";
14
15
  import { PositionEvent } from "./events/Position.sol";
15
16
  import { ReceivedEvent } from "./events/Received.sol";
16
17
  import { EventEmitter } from "./events/Emitter.sol";
@@ -20,7 +21,7 @@ import { IntroductionEvent } from "./events/Introduction.sol";
20
21
  import { LabeledEvent } from "./events/Labeled.sol";
21
22
  import { LockedEvent } from "./events/Locked.sol";
22
23
  import { NodeEvent } from "./events/Node.sol";
23
- import { PeerEvent } from "./events/Peer.sol";
24
+ import { PortEvent } from "./events/Port.sol";
24
25
  import { QueryEvent } from "./events/Query.sol";
25
26
  import { RootedEvent } from "./events/Rooted.sol";
26
27
  import { RouteEvent } from "./events/Route.sol";
package/README.md CHANGED
@@ -271,8 +271,8 @@ blocks executed in order within one transaction:
271
271
 
272
272
  Each step names a target command, the resources it may spend, and its request.
273
273
  The state threads through: whatever one command returns becomes the input
274
- state of the next, and the final state must be empty value cannot be left
275
- dangling at the end of a pipeline. This is the core of `Pipeline.pipe`:
274
+ state of the next, and the final state must be empty. This is the core of
275
+ `Pipeline.pipe`:
276
276
 
277
277
  ```solidity
278
278
  while (input.i < input.len) {
@@ -304,19 +304,19 @@ response: #accountAmount { bytes32 account, bytes32 asset, uint amount }
304
304
  Like commands, every query announces its request and response schemas at
305
305
  deployment, so tooling knows how to call it without artifacts.
306
306
 
307
- ## Peers
307
+ ## Ports
308
308
 
309
- Peers are the host-to-host surfaces, callable only by trusted hosts. The two
309
+ Ports are the host-to-host surfaces, callable only by trusted peer hosts. The two
310
310
  central ones are batches all the way down:
311
311
 
312
- - `peerSettle` consumes `#transaction { bytes32 from, bytes32 to, bytes32 asset,
312
+ - `portSettle` consumes `#transaction { bytes32 from, bytes32 to, bytes32 asset,
313
313
  uint amount }` blocks, debiting `from` and crediting `to` per
314
314
  block — how two hosts record settlement between their ledgers.
315
- - `peerPipePayable` consumes `#pipe` blocks, each carrying an account, an
315
+ - `portPipePayable` consumes `#context` blocks, each carrying an account, an
316
316
  initial state, and a run of steps — a complete pipeline delivered by another
317
- host, executed locally with its own resource budget.
317
+ host, executed locally against the peer call's shared value budget.
318
318
 
319
- This is also the cross-chain mechanism. `relayPayable` (or `peerDispatchPayable`)
319
+ This is also the cross-chain mechanism. `relayPayable` (or `portDispatchPayable`)
320
320
  wraps a pipe and addresses it to a chain; a bridge adapter moves the **raw
321
321
  bytes**; the destination host parses them with the same cursor rules and runs
322
322
  the same pipeline loop. Nothing in the payload is EVM-specific — step targets
@@ -363,7 +363,7 @@ Repo layout:
363
363
 
364
364
  - `contracts/core` — host, access control, balances, pipeline, validation
365
365
  - `contracts/commands` — standard commands and admin commands
366
- - `contracts/peer` — peer surfaces for inter-host and cross-chain flows
366
+ - `contracts/ports` — peer surfaces for inter-host and cross-chain flows
367
367
  - `contracts/guards` — guardian direct actions
368
368
  - `contracts/queries` — read-only query endpoints
369
369
  - `contracts/blocks` — block schema, cursor parsing, writers
@@ -548,28 +548,13 @@ library Cursors {
548
548
  return createBlock(Keys.Context, bytes.concat(account, toBytesBlock(state), toBytesBlock(request)));
549
549
  }
550
550
 
551
- /// @notice Encode a PIPE block.
552
- /// @param resources Chain resources assigned to the pipe.
553
- /// @param account Command account identifier.
554
- /// @param state Embedded state block stream.
555
- /// @param steps Embedded step block stream.
556
- /// @return Encoded PIPE block bytes.
557
- function toPipeBlock(
558
- uint resources,
559
- bytes32 account,
560
- bytes memory state,
561
- bytes memory steps
562
- ) internal pure returns (bytes memory) {
563
- return createBlock(Keys.Pipe, bytes.concat(bytes32(resources), toContextBlock(account, state, steps)));
564
- }
565
-
566
551
  /// @notice Encode a RELAY block.
567
552
  /// @param chain Destination chain node ID.
568
- /// @param resources Chain-adapter-specific resources for the destination pipe.
569
- /// @param steps Nested step block stream.
553
+ /// @param resources Chain-adapter-specific resources for the destination context.
554
+ /// @param request Nested request block stream.
570
555
  /// @return Encoded RELAY block bytes.
571
- function toRelayBlock(uint chain, uint resources, bytes memory steps) internal pure returns (bytes memory) {
572
- return createBlock(Keys.Relay, bytes.concat(bytes32(chain), bytes32(resources), toBytesBlock(steps)));
556
+ function toRelayBlock(uint chain, uint resources, bytes memory request) internal pure returns (bytes memory) {
557
+ return createBlock(Keys.Relay, bytes.concat(bytes32(chain), bytes32(resources), toBytesBlock(request)));
573
558
  }
574
559
 
575
560
  /// @notice Encode a DISPATCH block.
@@ -1155,33 +1140,18 @@ library Cursors {
1155
1140
  cur.exit(end);
1156
1141
  }
1157
1142
 
1158
- /// @notice Consume a PIPE block and return its resources and context fields.
1159
- /// @param cur Cursor; advanced past the block.
1160
- /// @return resources Chain resources assigned to the pipe.
1161
- /// @return account Command account identifier.
1162
- /// @return state Embedded state block stream.
1163
- /// @return steps Embedded step block stream.
1164
- function unpackPipe(
1165
- Cur memory cur
1166
- ) internal pure returns (uint resources, bytes32 account, bytes calldata state, bytes calldata steps) {
1167
- uint end = cur.enter(Keys.Pipe, 32 + Sizes.Header + 32 + 2 * Sizes.Header, 0);
1168
- resources = uint(cur.read32());
1169
- (account, state, steps) = cur.unpackContext();
1170
- cur.exit(end);
1171
- }
1172
-
1173
- /// @notice Consume a RELAY block and return its destination chain, resources, and step stream.
1143
+ /// @notice Consume a RELAY block and return its destination chain, resources, and request stream.
1174
1144
  /// @param cur Cursor; advanced past the block.
1175
1145
  /// @return chain Destination chain node ID.
1176
- /// @return resources Chain-adapter-specific resources for the destination pipe.
1177
- /// @return steps Embedded step block stream.
1146
+ /// @return resources Chain-adapter-specific resources for the destination context.
1147
+ /// @return request Embedded request block stream.
1178
1148
  function unpackRelay(
1179
1149
  Cur memory cur
1180
- ) internal pure returns (uint chain, uint resources, bytes calldata steps) {
1150
+ ) internal pure returns (uint chain, uint resources, bytes calldata request) {
1181
1151
  uint end = cur.enter(Keys.Relay, 64 + Sizes.Header, 0);
1182
1152
  chain = cur.readUint();
1183
1153
  resources = cur.readUint();
1184
- steps = cur.unpackBytes();
1154
+ request = cur.unpackBytes();
1185
1155
  cur.exit(end);
1186
1156
  }
1187
1157
 
@@ -1200,6 +1170,23 @@ library Cursors {
1200
1170
  cur.exit(end);
1201
1171
  }
1202
1172
 
1173
+ /// @notice Consume a CONTEXT_RECOVERY block and return its port, key, resources, and embedded context cursor.
1174
+ /// @param cur Cursor; advanced past the block.
1175
+ /// @return port Recovery handler port node ID.
1176
+ /// @return key Commitment or recovery lookup key.
1177
+ /// @return resources Chain resources assigned to the recovery attempt.
1178
+ /// @return context Cursor scoped to the embedded CONTEXT witness block.
1179
+ function unpackContextRecovery(
1180
+ Cur memory cur
1181
+ ) internal pure returns (uint port, bytes32 key, uint resources, Cur memory context) {
1182
+ uint end = cur.enter(Keys.ContextRecovery, 96 + Sizes.Header, 0);
1183
+ port = cur.readUint();
1184
+ key = cur.read32();
1185
+ resources = cur.readUint();
1186
+ context = cur.take(Keys.Context);
1187
+ cur.exit(end);
1188
+ }
1189
+
1203
1190
  // Type-specific validators
1204
1191
 
1205
1192
  /// @notice Validate an AUTH block at position `i` and extract deadline and proof.
@@ -1388,21 +1375,21 @@ library Cursors {
1388
1375
  // Transform helpers
1389
1376
  // -------------------------------------------------------------------------
1390
1377
 
1391
- /// @notice Consume a RELAY block and encode its destination pipe payload.
1378
+ /// @notice Consume a RELAY block and encode its destination context payload.
1392
1379
  /// @param cur Cursor; advanced past the RELAY block.
1393
- /// @param account Account identifier to embed in the destination pipe context.
1394
- /// @param state State block stream to embed in the destination pipe context.
1380
+ /// @param account Account identifier to embed in the destination context.
1381
+ /// @param state State block stream to embed in the destination context.
1395
1382
  /// @return chain Destination chain node ID.
1396
- /// @return resources Chain resources assigned to the destination pipe.
1397
- /// @return pipe Encoded PIPE block containing `account`, `state`, and relay steps.
1398
- function relayToPipe(
1383
+ /// @return resources Chain resources assigned to the destination context.
1384
+ /// @return context Encoded CONTEXT block containing `account`, `state`, and relay request.
1385
+ function relayToContext(
1399
1386
  Cur memory cur,
1400
1387
  bytes32 account,
1401
1388
  bytes calldata state
1402
- ) internal pure returns (uint chain, uint resources, bytes memory pipe) {
1403
- bytes calldata steps;
1404
- (chain, resources, steps) = cur.unpackRelay();
1405
- pipe = toPipeBlock(resources, account, bytes(state), bytes(steps));
1389
+ ) internal pure returns (uint chain, uint resources, bytes memory context) {
1390
+ bytes calldata request;
1391
+ (chain, resources, request) = cur.unpackRelay();
1392
+ context = toContextBlock(account, bytes(state), bytes(request));
1406
1393
  }
1407
1394
 
1408
1395
  // -------------------------------------------------------------------------
package/blocks/Keys.sol CHANGED
@@ -41,16 +41,16 @@ library Keys {
41
41
  bytes4 constant Transaction = bytes4(keccak256("#transaction"));
42
42
  /// @dev Sub-command invocation - (uint target, uint resources, #bytes as request)
43
43
  bytes4 constant Step = bytes4(keccak256("#step"));
44
- /// @dev Cross-chain pipe relay - (uint chain, uint resources, #bytes as steps)
44
+ /// @dev Cross-chain relay request - (uint chain, uint resources, #bytes as request)
45
45
  bytes4 constant Relay = bytes4(keccak256("#relay"));
46
+ /// @dev Command context transport - (bytes32 account, #bytes as state, #bytes as request)
47
+ bytes4 constant Context = bytes4(keccak256("#context"));
48
+ /// @dev Recoverable context witness - (uint port, bytes32 key, uint resources, #context)
49
+ bytes4 constant ContextRecovery = bytes4(keccak256("#contextRecovery"));
46
50
  /// @dev Cross-chain encoded payload dispatch - (uint chain, uint resources, #bytes as payload)
47
51
  bytes4 constant Dispatch = bytes4(keccak256("#dispatch"));
48
52
  /// @dev Raw external call - (uint target, uint resources, #bytes as payload)
49
53
  bytes4 constant Call = bytes4(keccak256("#call"));
50
- /// @dev Command context transport - (bytes32 account, #bytes as state, #bytes as request)
51
- bytes4 constant Context = bytes4(keccak256("#context"));
52
- /// @dev Pipeline invocation - (uint resources, #context)
53
- bytes4 constant Pipe = bytes4(keccak256("#pipe"));
54
54
  /// @dev Authentication proof - (uint cid, uint deadline, #bytes as proof); must appear last in its segment
55
55
  bytes4 constant Auth = bytes4(keccak256("#auth"));
56
56
  /// @dev Asset descriptor without amount - (bytes32 asset)
package/blocks/Schema.sol CHANGED
@@ -21,6 +21,8 @@ pragma solidity ^0.8.33;
21
21
  // stable format everywhere. EVM resources use the low 128 bits as native value.
22
22
  // - dotted field names and aliases, e.g. `dst.chain` or `#bytes as dst.payload`,
23
23
  // are offchain projection metadata only and do not change runtime encoding
24
+ // - a child block without an inline body, e.g. `#context as witness`, may resolve
25
+ // to a known schema in the active schema context; unresolved aliases are invalid
24
26
  // - fixed fields are packed in declaration order
25
27
  // - blocks have fixed fields followed by a dynamic child-block tail
26
28
  // - child block tails are embedded directly, without an extra stream wrapper
@@ -69,10 +71,10 @@ library Schemas {
69
71
  string constant Allowance = "#allowance { uint host, bytes32 asset, uint amount }";
70
72
  string constant Transaction = "#transaction { bytes32 from, bytes32 to, bytes32 asset, uint amount }";
71
73
  string constant Context = "#context { bytes32 account, #bytes as state, #bytes as request }";
72
- string constant Pipe = "#pipe { uint resources, #context { bytes32 account, #bytes as state, #bytes as steps } }";
74
+ string constant ContextRecovery = "#contextRecovery { uint port, bytes32 key, uint resources, #context as witness }";
73
75
  string constant Call = "#call { uint target, uint resources, #bytes as payload }";
74
76
  string constant Step = "#step { uint target, uint resources, #bytes as request }";
75
- string constant Relay = "#relay { uint chain, uint resources, #bytes as steps }";
77
+ string constant Relay = "#relay { uint chain, uint resources, #bytes as request }";
76
78
  string constant Dispatch = "#dispatch { uint chain, uint resources, #bytes as payload }";
77
79
  string constant Bounty = "#bounty { uint amount, bytes32 relayer }";
78
80
  string constant Fee = "#fee { uint amount }";
@@ -28,7 +28,6 @@ library Hints {
28
28
  uint constant Step = 256;
29
29
  uint constant Call = 256;
30
30
  uint constant Context = 512;
31
- uint constant Pipe = 608;
32
31
  }
33
32
 
34
33
  /// @title Writers
@@ -233,14 +232,6 @@ library Writers {
233
232
  return allocFromHint(count, Hints.Context);
234
233
  }
235
234
 
236
- /// @notice Allocate a writer for `count` PIPE blocks using a per-block capacity hint.
237
- /// @dev The backing buffer expands automatically if encoded pipes exceed the initial hint.
238
- /// @param count Number of pipe blocks to allocate space for.
239
- /// @return writer Allocated growable writer.
240
- function allocPipes(uint count) internal pure returns (Writer memory writer) {
241
- return allocFromHint(count, Hints.Pipe);
242
- }
243
-
244
235
  // -------------------------------------------------------------------------
245
236
  // Fixed-width write helpers
246
237
  // -------------------------------------------------------------------------
@@ -905,32 +896,6 @@ library Writers {
905
896
  appendBlock32BytesBytes(writer, Keys.Context, account, state, request);
906
897
  }
907
898
 
908
- /// @notice Append a PIPE block with a nested CONTEXT block.
909
- /// @param writer Destination writer; `i` is advanced by the encoded PIPE block length.
910
- /// @param resources Chain resources assigned to the pipe.
911
- /// @param account Command account identifier.
912
- /// @param state Raw nested state payload.
913
- /// @param steps Raw nested step payload.
914
- function appendPipe(
915
- Writer memory writer,
916
- uint resources,
917
- bytes32 account,
918
- bytes memory state,
919
- bytes memory steps
920
- ) internal pure {
921
- uint i = writer.i;
922
- uint len = 64 + 3 * Sizes.Header + state.length + steps.length;
923
- uint next = i + Sizes.Header + len;
924
- i = reserve(writer, next, next);
925
-
926
- uint p = writeHeader(writer.dst, i, Keys.Pipe, uint32(max32(len)));
927
- assembly ("memory-safe") {
928
- mstore(add(p, 0x08), resources)
929
- }
930
-
931
- writeBlock32BytesBytes(writer.dst, i + Sizes.Header + 32, Keys.Context, account, state, steps);
932
- }
933
-
934
899
  /// @notice Append a STATUS form block.
935
900
  /// @param writer Destination writer; `i` is advanced by `Sizes.Status`.
936
901
  /// @param code Status code to encode.
@@ -81,7 +81,7 @@ abstract contract DepositPayable is CommandBase, Payable, DepositPayableHook {
81
81
  ) external payable onlyCommand returns (bytes memory) {
82
82
  (Cur memory request, uint groups, ) = Cursors.init(c.request, 1);
83
83
  Writer memory writer = Writers.allocBalances(groups);
84
- Budget memory budget = valueBudget();
84
+ Budget memory budget = openValue();
85
85
 
86
86
  while (request.i < request.len) {
87
87
  (bytes32 asset, uint amount) = request.unpackAmount();
@@ -89,7 +89,7 @@ abstract contract DepositPayable is CommandBase, Payable, DepositPayableHook {
89
89
  writer.appendBalance(asset, amount);
90
90
  }
91
91
 
92
- settleValue(c.account, budget);
92
+ closeValue(c.account, budget);
93
93
  request.complete();
94
94
  return writer.finish();
95
95
  }
@@ -78,7 +78,7 @@ abstract contract ProvisionPayable is CommandBase, Payable, ProvisionPayableHook
78
78
  ) external payable onlyCommand returns (bytes memory) {
79
79
  (Cur memory request, uint groups, ) = Cursors.init(c.request, 1);
80
80
  Writer memory writer = Writers.allocCustodies(groups);
81
- Budget memory budget = valueBudget();
81
+ Budget memory budget = openValue();
82
82
 
83
83
  while (request.i < request.len) {
84
84
  HostAmount memory allocation = request.unpackAllocationValue();
@@ -86,7 +86,7 @@ abstract contract ProvisionPayable is CommandBase, Payable, ProvisionPayableHook
86
86
  writer.appendCustody(allocation);
87
87
  }
88
88
 
89
- settleValue(c.account, budget);
89
+ closeValue(c.account, budget);
90
90
  request.complete();
91
91
  return writer.finish();
92
92
  }
@@ -0,0 +1,48 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import {CommandBase, CommandContext, Keys} from "./Base.sol";
5
+ import {Payable} from "../core/Payable.sol";
6
+ import {Cursors, Cur, Schemas} from "../Cursors.sol";
7
+ import {Budget} from "../utils/Value.sol";
8
+
9
+ using Cursors for Cur;
10
+
11
+ abstract contract RecoverContextPayableHook {
12
+ /// @notice Override to recover one committed context witness.
13
+ /// @param port Recovery handler port node ID.
14
+ /// @param key Commitment or recovery lookup key.
15
+ /// @param resources Chain resources assigned to the recovery attempt.
16
+ /// @param context Cursor scoped to the embedded CONTEXT witness block.
17
+ /// @param budget Mutable native-value budget available to the recovery attempt.
18
+ function recoverContext(uint port, bytes32 key, uint resources, Cur memory context, Budget memory budget) internal virtual;
19
+ }
20
+
21
+ /// @title RecoverContextPayable
22
+ /// @notice Command that forwards ContextRecovery request blocks to a virtual hook.
23
+ /// Produces no output state.
24
+ abstract contract RecoverContextPayable is CommandBase, Payable, RecoverContextPayableHook {
25
+ uint internal immutable recoverContextPayableId = commandId(this.recoverContextPayable.selector);
26
+
27
+ constructor() {
28
+ emit Command(host, recoverContextPayableId, "1:0:0", Schemas.ContextRecovery, Keys.Empty, Keys.Empty, true);
29
+ emit Labeled(recoverContextPayableId, bytes32(0), "recoverContextPayable");
30
+ }
31
+
32
+ /// @notice Recover each ContextRecovery block in the command request.
33
+ /// @param c Command context; `c.request` must contain ContextRecovery blocks.
34
+ /// @return Empty output state.
35
+ function recoverContextPayable(CommandContext calldata c) external payable onlyCommand returns (bytes memory) {
36
+ (Cur memory request, , ) = Cursors.init(c.request, 1);
37
+ Budget memory budget = openValue();
38
+
39
+ while (request.i < request.len) {
40
+ (uint port, bytes32 key, uint resources, Cur memory context) = request.unpackContextRecovery();
41
+ recoverContext(port, key, resources, context, budget);
42
+ }
43
+
44
+ closeValue(c.account, budget);
45
+ request.complete();
46
+ return "";
47
+ }
48
+ }
@@ -37,12 +37,12 @@ abstract contract RelayPayable is CommandBase, Payable, DispatchPayableHook {
37
37
  /// @return output Empty output state.
38
38
  function relayPayable(CommandContext calldata c) external payable onlyCommand returns (bytes memory output) {
39
39
  (Cur memory request, ) = Cursors.init(c.request, 1, 1);
40
- Budget memory budget = valueBudget();
40
+ Budget memory budget = openValue();
41
41
 
42
- (uint chain, uint resources, bytes memory pipe) = request.relayToPipe(c.account, c.state);
43
- dispatch(chain, resources, pipe, budget);
42
+ (uint chain, uint resources, bytes memory context) = request.relayToContext(c.account, c.state);
43
+ dispatch(chain, resources, context, budget);
44
44
 
45
- settleValue(c.account, budget);
45
+ closeValue(c.account, budget);
46
46
  request.complete();
47
47
  return "";
48
48
  }
@@ -27,7 +27,7 @@ abstract contract ExecutePayable is AdminBase, Payable {
27
27
  /// @return Empty output state.
28
28
  function executePayable(CommandContext calldata c) external payable onlyAdmin(c.account) returns (bytes memory) {
29
29
  (Cur memory request, , ) = Cursors.init(c.request, 1);
30
- Budget memory budget = valueBudget();
30
+ Budget memory budget = openValue();
31
31
 
32
32
  while (request.i < request.len) {
33
33
  (uint target, uint resources, bytes calldata data) = request.unpackCall();
@@ -4,7 +4,7 @@ pragma solidity ^0.8.33;
4
4
  import {CommitmentEvent} from "../events/Commitment.sol";
5
5
 
6
6
  /// @title Commitments
7
- /// @notice On-chain registry for account-scoped digest commitments.
7
+ /// @notice On-chain registry for digest commitments.
8
8
  abstract contract Commitments is CommitmentEvent {
9
9
  /// @dev key -> committed digest.
10
10
  mapping(bytes32 key => bytes32 digest) internal commitments;
package/core/Payable.sol CHANGED
@@ -5,16 +5,14 @@ import {Budget, Values} from "../utils/Value.sol";
5
5
 
6
6
  /// @title Payable
7
7
  /// @notice Abstract mixin for entrypoints that accept native value (`msg.value`).
8
- /// Provides a shared settlement hook for any unspent value remaining in the
9
- /// mutable budget after execution completes.
8
+ /// Provides shared helpers for mutable native-value budgets.
10
9
  abstract contract Payable {
11
10
  /// @dev Thrown when a payable entrypoint completes with unspent native value.
12
- /// Override `settleValue` to implement refund or forwarding behavior instead.
13
11
  error UnusedValue(uint remaining);
14
12
 
15
- /// @notice Create a native-value budget from the current call's `msg.value`.
16
- /// @return Budget initialised with the full `msg.value`.
17
- function valueBudget() internal view returns (Budget memory) {
13
+ /// @notice Open a native-value budget from the current call's `msg.value`.
14
+ /// @return Budget initialized with the full `msg.value`.
15
+ function openValue() internal view returns (Budget memory) {
18
16
  return Budget({remaining: msg.value});
19
17
  }
20
18
 
@@ -24,36 +22,25 @@ abstract contract Payable {
24
22
  /// @param resources Packed chain resources.
25
23
  /// @return value Native value to forward in wei.
26
24
  function useValue(Budget memory budget, uint resources) internal pure returns (uint128 value) {
27
- return Values.use(budget, uint128(resources));
25
+ value = uint128(resources);
26
+ Values.use(budget, value);
28
27
  }
29
28
 
30
- /// @notice Deduct the EVM value lane from a packed resource word as a new sub-budget.
31
- /// @dev EVM resources use the low 128 bits as native value/endowment.
32
- /// @param budget Mutable parent budget to deduct from.
33
- /// @param resources Packed chain resources.
34
- /// @return A new budget with the EVM value lane remaining.
35
- function allocateValue(Budget memory budget, uint resources) internal pure returns (Budget memory) {
36
- return Values.allocate(budget, uint128(resources));
37
- }
38
-
39
- /// @notice Drains the budget and settles any remaining native value.
40
- /// @dev Calls the amount-based `settleValue` hook only when some value remains.
29
+ /// @notice Close a native-value budget and settle any drained value.
41
30
  /// @param account Account identifier for the current invocation.
42
- /// @param budget Mutable native-value budget used during execution.
43
- function settleValue(bytes32 account, Budget memory budget) internal {
44
- uint value = budget.remaining;
31
+ /// @param budget Mutable native-value budget to close.
32
+ function closeValue(bytes32 account, Budget memory budget) internal {
33
+ uint value = Values.drain(budget);
45
34
  if (value == 0) return;
46
- budget.remaining = 0;
47
35
  settleValue(account, value);
48
36
  }
49
37
 
50
- /// @notice Handles leftover native value after payable execution has finished.
51
- /// @dev Override this hook to refund or redirect unused value.
52
- /// The default implementation rejects any leftover amount.
38
+ /// @notice Handle a drained native value amount.
39
+ /// @dev Override to refund or redirect unused value. The default rejects it.
53
40
  /// @param account Account identifier for the current invocation.
54
- /// @param remaining Unspent native value left in the budget, in wei.
55
- function settleValue(bytes32 account, uint remaining) internal virtual {
41
+ /// @param value Drained native value amount to settle, in wei.
42
+ function settleValue(bytes32 account, uint value) internal virtual {
56
43
  account;
57
- revert UnusedValue(remaining);
44
+ revert UnusedValue(value);
58
45
  }
59
46
  }
package/core/Pipeline.sol CHANGED
@@ -32,6 +32,7 @@ abstract contract Pipeline is Payable {
32
32
 
33
33
  /// @notice Execute a STEP block stream through the pipeline.
34
34
  /// @dev Reverts with `UnexpectedState` if the final threaded state is non-empty.
35
+ /// Callers remain responsible for settling any unspent value in `budget`.
35
36
  /// @param account Account identifier used for each dispatched step.
36
37
  /// @param state Initial state block stream passed to the first step.
37
38
  /// @param steps STEP block stream to execute.
@@ -50,7 +51,6 @@ abstract contract Pipeline is Payable {
50
51
  }
51
52
 
52
53
  if (state.length != 0) revert UnexpectedState();
53
- settleValue(account, budget);
54
54
  input.complete();
55
55
  }
56
56
  }
package/docs/Schema.md CHANGED
@@ -58,7 +58,6 @@ Once a child block appears, no more fixed fields may follow.
58
58
  ```txt
59
59
  #call { uint target, uint resources, #bytes as payload }
60
60
  #context { bytes32 account, #bytes as state, #bytes as request }
61
- #pipe { uint resources, #context { bytes32 account, #bytes as state, #bytes as steps } }
62
61
  ```
63
62
 
64
63
  The tail is embedded directly as child block bytes. There is no wrapper around a
@@ -118,6 +117,18 @@ maybe #account { bytes32 account } as recipient
118
117
 
119
118
  Aliases may be used on any block item, including child blocks and prime items.
120
119
 
120
+ A child block without an inline body may also be used as a schema reference:
121
+
122
+ ```txt
123
+ #contextRecovery { uint port, bytes32 key, uint resources, #context as witness }
124
+ ```
125
+
126
+ Alias resolution is context-dependent. A consumer may resolve `#context` from the
127
+ standard `Schemas` table, from app-specific schemas, or from another active
128
+ schema context. Consumers should reject schemas with unresolved aliases. The
129
+ runtime encoding is still an embedded child block with the referenced key and
130
+ layout.
131
+
121
132
  ## Field Paths
122
133
 
123
134
  Field names and aliases may use dotted paths for offchain projection. A dotted
@@ -271,7 +282,7 @@ Common protocol schemas live in `contracts/blocks/Schema.sol`:
271
282
  #call { uint target, uint resources, #bytes as payload }
272
283
  #step { uint target, uint resources, #bytes as request }
273
284
  #context { bytes32 account, #bytes as state, #bytes as request }
274
- #pipe { uint resources, #context { bytes32 account, #bytes as state, #bytes as steps } }
285
+ #contextRecovery { uint port, bytes32 key, uint resources, #context as witness }
275
286
  #auth { uint cid, uint deadline, #bytes as proof }
276
287
  ```
277
288