@rootzero/contracts 0.9.8 → 1.0.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 (56) hide show
  1. package/Core.sol +1 -1
  2. package/Endpoints.sol +3 -1
  3. package/Events.sol +2 -0
  4. package/README.md +19 -18
  5. package/blocks/Cursors.sol +152 -162
  6. package/blocks/Keys.sol +10 -6
  7. package/blocks/Schema.sol +17 -9
  8. package/blocks/Writers.sol +4 -4
  9. package/commands/Burn.sol +7 -4
  10. package/commands/Credit.sol +9 -9
  11. package/commands/Debit.sol +7 -4
  12. package/commands/Deposit.sol +14 -8
  13. package/commands/Payout.sol +49 -0
  14. package/commands/Provision.sol +14 -8
  15. package/commands/Relay.sol +58 -0
  16. package/commands/Withdraw.sol +10 -9
  17. package/commands/admin/AllowAssets.sol +9 -4
  18. package/commands/admin/Allowance.sol +7 -4
  19. package/commands/admin/Appoint.sol +7 -4
  20. package/commands/admin/Authorize.sol +7 -4
  21. package/commands/admin/DenyAssets.sol +9 -4
  22. package/commands/admin/Destroy.sol +5 -3
  23. package/commands/admin/Dismiss.sol +7 -4
  24. package/commands/admin/Execute.sol +8 -5
  25. package/commands/admin/Init.sol +5 -3
  26. package/commands/admin/Unauthorize.sol +7 -4
  27. package/core/Host.sol +10 -1
  28. package/core/Pipeline.sol +4 -3
  29. package/core/Runtime.sol +3 -36
  30. package/core/Types.sol +1 -1
  31. package/docs/Schema.md +0 -1
  32. package/events/Admin.sol +11 -8
  33. package/events/Chain.sol +20 -0
  34. package/events/Command.sol +11 -8
  35. package/events/Peer.sol +5 -5
  36. package/events/Query.sol +2 -4
  37. package/events/Transfer.sol +22 -0
  38. package/guards/Revoke.sol +3 -3
  39. package/package.json +1 -1
  40. package/peer/AllowAssets.sol +5 -3
  41. package/peer/Allowance.sol +5 -3
  42. package/peer/BalancePull.sol +5 -3
  43. package/peer/DenyAssets.sol +5 -3
  44. package/peer/Dispatch.sol +51 -0
  45. package/peer/Pipe.sol +7 -5
  46. package/peer/Settle.sol +13 -8
  47. package/queries/Assets.sol +3 -3
  48. package/queries/Balances.sol +3 -3
  49. package/queries/Positions.sol +3 -3
  50. package/utils/Accounts.sol +6 -31
  51. package/utils/Actions.sol +11 -9
  52. package/utils/Assets.sol +21 -21
  53. package/utils/Ids.sol +12 -2
  54. package/utils/Layout.sol +21 -17
  55. package/utils/Utils.sol +2 -2
  56. package/commands/Transfer.sol +0 -54
package/Core.sol CHANGED
@@ -5,7 +5,7 @@ pragma solidity ^0.8.33;
5
5
  // Import this file to bring the full rootzero host base layer into scope.
6
6
 
7
7
  import { AccessControl } from "./core/Access.sol";
8
- import { Balances } from "./core/Balances.sol";
8
+ import { Balances, InsufficientFunds } from "./core/Balances.sol";
9
9
  import { Runtime } from "./core/Runtime.sol";
10
10
  import { Host, IHostIntroduction } from "./core/Host.sol";
11
11
  import { FailedCall, NodeCalls } from "./core/Calls.sol";
package/Endpoints.sol CHANGED
@@ -14,8 +14,9 @@ import { Burn, BurnHook } from "./commands/Burn.sol";
14
14
  import { CreditAccount, CreditAccountHook } from "./commands/Credit.sol";
15
15
  import { DebitAccount, DebitAccountHook } from "./commands/Debit.sol";
16
16
  import { Deposit, DepositHook, DepositPayable, DepositPayableHook } from "./commands/Deposit.sol";
17
+ import { Payout, PayoutHook } from "./commands/Payout.sol";
17
18
  import { Provision, ProvisionHook, ProvisionPayable, ProvisionPayableHook } from "./commands/Provision.sol";
18
- import { Transfer, TransferHook } from "./commands/Transfer.sol";
19
+ import { RelayPayable, RelayPayableHook } from "./commands/Relay.sol";
19
20
  import { Withdraw, WithdrawHook } from "./commands/Withdraw.sol";
20
21
 
21
22
  // Admin commands
@@ -37,6 +38,7 @@ import { PeerAllowance } from "./peer/Allowance.sol";
37
38
  import { PeerBalancePull, BalancePullHook } from "./peer/BalancePull.sol";
38
39
  import { PeerDenyAssets } from "./peer/DenyAssets.sol";
39
40
  import { PeerPipePayable } from "./peer/Pipe.sol";
41
+ import { PeerDispatchPayable } from "./peer/Dispatch.sol";
40
42
  import { PeerSettle } from "./peer/Settle.sol";
41
43
 
42
44
  // Guard endpoints
package/Events.sol CHANGED
@@ -8,6 +8,7 @@ import { AdminEvent } from "./events/Admin.sol";
8
8
  import { AssetStatusEvent } from "./events/Asset.sol";
9
9
  import { Actions } from "./utils/Actions.sol";
10
10
  import { BalanceEvent } from "./events/Balance.sol";
11
+ import { ChainEvent } from "./events/Chain.sol";
11
12
  import { CommandEvent } from "./events/Command.sol";
12
13
  import { PositionEvent } from "./events/Position.sol";
13
14
  import { ReceivedEvent } from "./events/Received.sol";
@@ -21,6 +22,7 @@ import { PeerEvent } from "./events/Peer.sol";
21
22
  import { QueryEvent } from "./events/Query.sol";
22
23
  import { RootedEvent } from "./events/Rooted.sol";
23
24
  import { SpentEvent } from "./events/Spent.sol";
25
+ import { TransferEvent } from "./events/Transfer.sol";
24
26
  import { UnlockedEvent } from "./events/Unlocked.sol";
25
27
 
26
28
 
package/README.md CHANGED
@@ -8,11 +8,11 @@ It contains the reusable contracts, utilities, cursor parsers, and encoding help
8
8
 
9
9
  Most consumers should start from the package root entry points:
10
10
 
11
- - `@rootzero/contracts/Core.sol` host, access control, balances, and validator building blocks
12
- - `@rootzero/contracts/Endpoints.sol` command, peer, guard, and query base contracts plus standard endpoint mixins
13
- - `@rootzero/contracts/Cursors.sol` cursor reader (`Cur`), block schemas, key constants, typed block helpers, and writers
14
- - `@rootzero/contracts/Utils.sol` IDs, assets, accounts, layout, and value helpers
15
- - `@rootzero/contracts/Events.sol` reusable event emitters and event contracts
11
+ - `@rootzero/contracts/Core.sol` - host, access control, balances, and validator building blocks
12
+ - `@rootzero/contracts/Endpoints.sol` - command, peer, guard, and query base contracts plus standard endpoint mixins
13
+ - `@rootzero/contracts/Cursors.sol` - cursor reader (`Cur`), block schemas, key constants, typed block helpers, and writers
14
+ - `@rootzero/contracts/Utils.sol` - IDs, assets, accounts, layout, and value helpers
15
+ - `@rootzero/contracts/Events.sol` - reusable event emitters and event contracts
16
16
 
17
17
  ## Block Wire Format
18
18
 
@@ -28,7 +28,7 @@ All request and response data is encoded as a binary block stream. Each block is
28
28
 
29
29
  Protocol blocks use schema strings and four-byte keys:
30
30
 
31
- - `Schemas` describes semantic protocol blocks such as `#amount`, `#balance`, `#custody`, and `#payout`.
31
+ - `Schemas` describes semantic protocol blocks such as `#amount`, `#balance`, `#custody`, and `#relay`.
32
32
  - `Forms` describes reusable structural blocks such as `#accountAsset` and `#accountAmount`, mostly used by queries.
33
33
  - `Keys` contains the runtime `bytes4` keys derived from block names.
34
34
 
@@ -77,21 +77,21 @@ abstract contract ExampleCommand is CommandBase {
77
77
  uint internal immutable myCommandId = commandId(NAME);
78
78
 
79
79
  constructor() {
80
- emit Command(host, myCommandId, NAME, "1:0:1", Schemas.Amount, Keys.Empty, Keys.Balance, false);
80
+ emit Command(host, myCommandId, NAME, "1:0:1", Schemas.Amount, Keys.Empty, Keys.Balance, false, false);
81
81
  }
82
82
 
83
83
  function myCommand(
84
84
  CommandContext calldata c
85
85
  ) external onlyCommand returns (bytes memory) {
86
- (Cur memory request, uint groups) = cursor(c.request, 1);
86
+ (Cur memory request, uint groups, ) = Cursors.init(c.request, 0, 1);
87
87
  Writer memory writer = Writers.allocBalances(groups);
88
88
 
89
- while (request.i < request.bound) {
89
+ while (request.i < request.len) {
90
90
  (bytes32 asset, bytes32 meta, uint amount) = request.unpackAmount();
91
91
  writer.appendBalance(asset, meta, amount);
92
92
  }
93
93
 
94
- request.close();
94
+ request.complete();
95
95
  return writer.finish();
96
96
  }
97
97
  }
@@ -99,14 +99,15 @@ abstract contract ExampleCommand is CommandBase {
99
99
 
100
100
  ## Repo Layout
101
101
 
102
- - `contracts/core` host, access control, balances, operation base, and signature validation
103
- - `contracts/commands` standard command building blocks and admin commands
104
- - `contracts/peer` peer protocol surfaces for inter-host asset flows and asset allow/deny
105
- - `contracts/blocks` block stream schema (`Schema`), cursor parsing (`Cursors`), and writers (`Writers`)
106
- - `contracts/utils` shared encoding helpers: IDs, assets, accounts, layout, ECDSA
107
- - `contracts/events` protocol event contracts and emitters
108
- - `contracts/interfaces` discovery interfaces and shared external protocol surfaces
109
- - `docs` introductory documentation
102
+ - `contracts/core` - host, access control, balances, operation base, and signature validation
103
+ - `contracts/commands` - standard command building blocks and admin commands
104
+ - `contracts/peer` - peer protocol surfaces for inter-host asset flows and asset allow/deny
105
+ - `contracts/guards` - guard action surfaces for delegated protection flows
106
+ - `contracts/queries` - read-only query endpoints for protocol state
107
+ - `contracts/blocks` - block stream schema (`Schema`), cursor parsing (`Cursors`), and writers (`Writers`)
108
+ - `contracts/utils` - shared encoding helpers: IDs, assets, accounts, layout, ECDSA
109
+ - `contracts/events` - protocol event contracts and emitters
110
+ - `docs` - introductory documentation
110
111
 
111
112
  ## Install and Compile
112
113
 
@@ -6,7 +6,7 @@ import {Sizes} from "./Schema.sol";
6
6
  import {Keys} from "./Keys.sol";
7
7
 
8
8
  /// @notice Zero-copy view into a calldata block stream.
9
- /// All positions (`i`, `bound`) are byte offsets relative to the start of the source region.
9
+ /// All positions (`i`) are byte offsets relative to the start of the source region.
10
10
  /// The absolute calldata location of byte `i` is `offset + i`.
11
11
  struct Cur {
12
12
  /// @dev Absolute calldata byte offset of the source region start.
@@ -15,9 +15,6 @@ struct Cur {
15
15
  uint i;
16
16
  /// @dev Total byte length of the source region.
17
17
  uint len;
18
- /// @dev Exclusive upper bound for the current iteration group, set by `primeRun`.
19
- /// Zero until `primeRun` is called.
20
- uint bound;
21
18
  }
22
19
 
23
20
  using Cursors for Cur;
@@ -32,11 +29,11 @@ library Cursors {
32
29
  error MalformedBlocks();
33
30
  /// @dev Current block key does not match the expected key, or payload size is out of range.
34
31
  error InvalidBlock();
35
- /// @dev `complete` called but the cursor has not consumed exactly up to `bound`.
32
+ /// @dev `complete` called but the cursor has not consumed exactly to `len`.
36
33
  error IncompleteCursor();
37
- /// @dev `primeRun` found zero blocks of the expected key; the cursor region is empty.
34
+ /// @dev `run` found zero blocks of the expected key; the cursor region is empty.
38
35
  error ZeroCursor();
39
- /// @dev `primeRun` was called with a zero group size.
36
+ /// @dev `run` was called with a zero group size.
40
37
  error ZeroGroup();
41
38
  /// @dev An account field was required but the block or fallback was zero.
42
39
  error ZeroAccount();
@@ -65,15 +62,50 @@ library Cursors {
65
62
  cur.len = source.length;
66
63
  }
67
64
 
68
- /// @notice Create a cursor and prime it for a grouped iteration pass.
69
- /// Equivalent to `open(source)` followed by `primeRun(group)`.
70
- /// @param source Calldata slice that forms the block stream.
65
+ /// @notice Create a cursor backed by `source[i:]`.
66
+ /// @param source Calldata slice that forms the parent block stream.
67
+ /// @param i Start byte offset within `source`.
68
+ /// @return cur Cursor positioned at the beginning of `source[i:]`.
69
+ function open(bytes calldata source, uint i) internal pure returns (Cur memory cur) {
70
+ return open(source[i:]);
71
+ }
72
+
73
+ /// @notice Create a cursor over `source[i:]` and restrict it to its first grouped run.
74
+ /// Equivalent to `open(source, i)`, reading the current key, then `run(key, group)`.
75
+ /// @param source Calldata slice that forms the parent block stream.
76
+ /// @param i Start byte offset within `source`.
71
77
  /// @param group Expected block group size (e.g. 1 for single, 2 for paired).
72
- /// @return cur Cursor with `bound` set to the end of the first run.
73
- /// @return groups Number of block groups in the run (`prime block count / group`).
74
- function init(bytes calldata source, uint group) internal pure returns (Cur memory cur, uint groups) {
75
- cur = open(source);
76
- (, groups) = cur.primeRun(group);
78
+ /// @return cur Cursor with `len` truncated to the end of the first run in `source[i:]`.
79
+ /// @return groups Number of block groups in the run (`block count / group`).
80
+ /// @return next Byte offset immediately after the run, relative to `source`.
81
+ function init(
82
+ bytes calldata source,
83
+ uint i,
84
+ uint group
85
+ ) internal pure returns (Cur memory cur, uint groups, uint next) {
86
+ cur = open(source, i);
87
+ if (cur.i == cur.len) revert ZeroCursor();
88
+ (bytes4 key, ) = cur.peek(cur.i);
89
+ groups = cur.run(key, group);
90
+ next = i + cur.len;
91
+ }
92
+
93
+ /// @notice Create a cursor over `source[i:]`, restrict it to its first grouped run, and require an exact group count.
94
+ /// @param source Calldata slice that forms the parent block stream.
95
+ /// @param i Start byte offset within `source`.
96
+ /// @param group Expected block group size (e.g. 1 for single, 2 for paired).
97
+ /// @param expectedGroups Required number of groups in the run.
98
+ /// @return cur Cursor with `len` truncated to the end of the first run in `source[i:]`.
99
+ /// @return next Byte offset immediately after the run, relative to `source`.
100
+ function init(
101
+ bytes calldata source,
102
+ uint i,
103
+ uint group,
104
+ uint expectedGroups
105
+ ) internal pure returns (Cur memory cur, uint next) {
106
+ uint groups;
107
+ (cur, groups, next) = init(source, i, group);
108
+ if (groups != expectedGroups) revert BadRatio();
77
109
  }
78
110
 
79
111
  /// @notice Move the cursor to an absolute position within the source region.
@@ -119,7 +151,7 @@ library Cursors {
119
151
  }
120
152
 
121
153
  /// @notice Return the full cursor region as a calldata slice.
122
- /// Does not advance the cursor; `cur.i` and `cur.bound` are ignored.
154
+ /// Does not advance the cursor; `cur.i` is ignored.
123
155
  /// @param cur Cursor whose backing region should be returned.
124
156
  /// @return data Calldata view over `[cur.offset, cur.offset + cur.len)`.
125
157
  function raw(Cur memory cur) internal pure returns (bytes calldata data) {
@@ -128,7 +160,7 @@ library Cursors {
128
160
  }
129
161
 
130
162
  /// @notice Return a sub-range of the cursor region as a calldata slice.
131
- /// Does not advance the cursor; `cur.i` and `cur.bound` are ignored.
163
+ /// Does not advance the cursor; `cur.i` is ignored.
132
164
  /// @param cur Source cursor.
133
165
  /// @param from Start byte offset within the source region (inclusive).
134
166
  /// @param to End byte offset within the source region (exclusive).
@@ -267,21 +299,19 @@ library Cursors {
267
299
  }
268
300
  }
269
301
 
270
- /// @notice Initialise the cursor for a grouped iteration pass.
271
- /// Reads the key of the first block, counts the consecutive run of that key,
272
- /// stores the run end in `cur.bound`, validates that the count is a
273
- /// multiple of `group`, and returns the run key and normalized group count.
274
- /// @param cur Cursor to prime; `cur.bound` is updated in place.
302
+ /// @notice Restrict the cursor to the consecutive run of `key` at its current position.
303
+ /// Counts the run, truncates `cur.len` to the run end, and validates that the
304
+ /// count is a multiple of `group`.
305
+ /// @param cur Cursor to restrict; `cur.len` is updated in place.
306
+ /// @param key Expected block type identifier of the run.
275
307
  /// @param group Expected group size (e.g. 1 for single-asset, 2 for paired input/output).
276
- /// @return key Block type identifier of the run.
277
308
  /// @return groups Number of groups represented by the run (`block count / group`).
278
- function primeRun(Cur memory cur, uint group) internal pure returns (bytes4 key, uint groups) {
309
+ function run(Cur memory cur, bytes4 key, uint group) internal pure returns (uint groups) {
279
310
  if (group == 0) revert ZeroGroup();
280
- key = cur.i + 4 > cur.len ? bytes4(0) : bytes4(msg.data[cur.offset + cur.i:cur.offset + cur.i + 4]);
281
- uint count;
282
- (count, cur.bound) = countRun(cur, cur.i, key);
311
+ (uint count, uint next) = countRun(cur, cur.i, key);
283
312
  if (count == 0) revert ZeroCursor();
284
313
  if (count % group != 0) revert BadRatio();
314
+ cur.len = next;
285
315
  groups = count / group;
286
316
  }
287
317
 
@@ -361,17 +391,6 @@ library Cursors {
361
391
  return maybeTake(cur, Keys.Data);
362
392
  }
363
393
 
364
- /// @notice Enter a List block, prime its member run, and return the group count.
365
- /// @param cur Cursor positioned at a list block; advanced past the 8-byte header.
366
- /// @param group Expected block group size for the list item stream.
367
- /// @return groups Number of block groups in the list payload.
368
- /// @return next Byte offset immediately after the list payload.
369
- function list(Cur memory cur, uint group) internal pure returns (uint groups, uint next) {
370
- next = list(cur);
371
- (, groups) = cur.primeRun(group);
372
- if (cur.bound != next) revert IncompleteCursor();
373
- }
374
-
375
394
  /// @notice Exit a nested region at an exact boundary.
376
395
  /// Reverts with `IncompleteCursor` if `end` exceeds the cursor region length
377
396
  /// or `cur.i != end`.
@@ -381,13 +400,6 @@ library Cursors {
381
400
  if (end > cur.len || cur.i != end) revert IncompleteCursor();
382
401
  }
383
402
 
384
- /// @notice Assert that the cursor has consumed exactly up to `bound`.
385
- /// Reverts with `IncompleteCursor` if `bound` is zero or `cur.i != cur.bound`.
386
- /// @param cur Cursor to check.
387
- function close(Cur memory cur) internal pure {
388
- if (cur.bound == 0 || cur.i != cur.bound) revert IncompleteCursor();
389
- }
390
-
391
403
  /// @notice Assert that the cursor has consumed its entire source region.
392
404
  /// Reverts with `IncompleteCursor` when `cur.i != cur.len`.
393
405
  /// @param cur Cursor to check.
@@ -527,7 +539,11 @@ library Cursors {
527
539
  /// @param state Embedded state block stream.
528
540
  /// @param request Embedded request block stream.
529
541
  /// @return Encoded CONTEXT block bytes.
530
- function toContextBlock(bytes32 account, bytes memory state, bytes memory request) internal pure returns (bytes memory) {
542
+ function toContextBlock(
543
+ bytes32 account,
544
+ bytes memory state,
545
+ bytes memory request
546
+ ) internal pure returns (bytes memory) {
531
547
  return createBlock(Keys.Context, bytes.concat(account, toBytesBlock(state), toBytesBlock(request)));
532
548
  }
533
549
 
@@ -535,15 +551,33 @@ library Cursors {
535
551
  /// @param value Native value assigned to the pipe.
536
552
  /// @param account Command account identifier.
537
553
  /// @param state Embedded state block stream.
538
- /// @param request Embedded request block stream.
554
+ /// @param steps Embedded step block stream.
539
555
  /// @return Encoded PIPE block bytes.
540
556
  function toPipeBlock(
541
557
  uint value,
542
558
  bytes32 account,
543
559
  bytes memory state,
544
- bytes memory request
560
+ bytes memory steps
545
561
  ) internal pure returns (bytes memory) {
546
- return createBlock(Keys.Pipe, bytes.concat(bytes32(value), toContextBlock(account, state, request)));
562
+ return createBlock(Keys.Pipe, bytes.concat(bytes32(value), toContextBlock(account, state, steps)));
563
+ }
564
+
565
+ /// @notice Encode a RELAY block.
566
+ /// @param chain Destination chain node ID.
567
+ /// @param endowment Native value requested for the destination pipe.
568
+ /// @param steps Nested step block stream.
569
+ /// @return Encoded RELAY block bytes.
570
+ function toRelayBlock(uint chain, uint endowment, bytes memory steps) internal pure returns (bytes memory) {
571
+ return createBlock(Keys.Relay, bytes.concat(bytes32(chain), bytes32(endowment), toBytesBlock(steps)));
572
+ }
573
+
574
+ /// @notice Encode a DISPATCH block.
575
+ /// @param chain Destination chain node ID.
576
+ /// @param endowment Native value requested for the destination dispatch.
577
+ /// @param payload Encoded cross-chain payload.
578
+ /// @return Encoded DISPATCH block bytes.
579
+ function toDispatchBlock(uint chain, uint endowment, bytes memory payload) internal pure returns (bytes memory) {
580
+ return createBlock(Keys.Dispatch, bytes.concat(bytes32(chain), bytes32(endowment), toBytesBlock(payload)));
547
581
  }
548
582
 
549
583
  // -------------------------------------------------------------------------
@@ -902,38 +936,6 @@ library Cursors {
902
936
  (value.asset, value.meta, value.amount) = unpackAssetAmount(cur, Keys.Balance);
903
937
  }
904
938
 
905
- /// @notice Consume a MINIMUM block and return its fields as separate values.
906
- /// @param cur Cursor; advanced past the block.
907
- /// @return asset Asset identifier.
908
- /// @return meta Asset metadata slot.
909
- /// @return amount Minimum acceptable amount.
910
- function unpackMinimum(Cur memory cur) internal pure returns (bytes32 asset, bytes32 meta, uint amount) {
911
- return unpackAssetAmount(cur, Keys.Minimum);
912
- }
913
-
914
- /// @notice Consume a MINIMUM block and return its fields as a struct.
915
- /// @param cur Cursor; advanced past the block.
916
- /// @return value Decoded asset, meta, and minimum amount.
917
- function unpackMinimumValue(Cur memory cur) internal pure returns (AssetAmount memory value) {
918
- (value.asset, value.meta, value.amount) = unpackAssetAmount(cur, Keys.Minimum);
919
- }
920
-
921
- /// @notice Consume a MAXIMUM block and return its fields as separate values.
922
- /// @param cur Cursor; advanced past the block.
923
- /// @return asset Asset identifier.
924
- /// @return meta Asset metadata slot.
925
- /// @return amount Maximum allowable spend.
926
- function unpackMaximum(Cur memory cur) internal pure returns (bytes32 asset, bytes32 meta, uint amount) {
927
- return unpackAssetAmount(cur, Keys.Maximum);
928
- }
929
-
930
- /// @notice Consume a MAXIMUM block and return its fields as a struct.
931
- /// @param cur Cursor; advanced past the block.
932
- /// @return value Decoded asset, meta, and maximum amount.
933
- function unpackMaximumValue(Cur memory cur) internal pure returns (AssetAmount memory value) {
934
- (value.asset, value.meta, value.amount) = unpackAssetAmount(cur, Keys.Maximum);
935
- }
936
-
937
939
  /// @notice Consume a HOST_ACCOUNT_ASSET form block and return its fields as separate values.
938
940
  /// @param cur Cursor; advanced past the block.
939
941
  /// @return host Host node ID.
@@ -953,25 +955,6 @@ library Cursors {
953
955
  (value.host, value.account, value.asset, value.meta) = unpackHostAccountAsset(cur, Keys.HostAccountAsset);
954
956
  }
955
957
 
956
- /// @notice Consume a PAYOUT block and return its fields as separate values.
957
- /// @param cur Cursor; advanced past the block.
958
- /// @return account Account identifier.
959
- /// @return asset Asset identifier.
960
- /// @return meta Asset metadata slot.
961
- /// @return amount Token amount.
962
- function unpackPayout(
963
- Cur memory cur
964
- ) internal pure returns (bytes32 account, bytes32 asset, bytes32 meta, uint amount) {
965
- return unpackAccountAmount(cur, Keys.Payout);
966
- }
967
-
968
- /// @notice Consume a PAYOUT block and return its fields as a struct.
969
- /// @param cur Cursor; advanced past the block.
970
- /// @return value Decoded account, asset, meta, and amount.
971
- function unpackPayoutValue(Cur memory cur) internal pure returns (AccountAmount memory value) {
972
- (value.account, value.asset, value.meta, value.amount) = unpackAccountAmount(cur, Keys.Payout);
973
- }
974
-
975
958
  /// @notice Consume an ACCOUNT_AMOUNT form block and return its fields as separate values.
976
959
  /// @param cur Cursor; advanced past the block.
977
960
  /// @return account Account identifier.
@@ -1102,7 +1085,9 @@ library Cursors {
1102
1085
  /// @return account Command account identifier.
1103
1086
  /// @return state Embedded state block stream.
1104
1087
  /// @return request Embedded request block stream.
1105
- function unpackContext(Cur memory cur) internal pure returns (bytes32 account, bytes calldata state, bytes calldata request) {
1088
+ function unpackContext(
1089
+ Cur memory cur
1090
+ ) internal pure returns (bytes32 account, bytes calldata state, bytes calldata request) {
1106
1091
  uint end = cur.enter(Keys.Context, 32 + 2 * Sizes.Header, 0);
1107
1092
  account = cur.read32();
1108
1093
  state = cur.unpackBytes();
@@ -1115,13 +1100,43 @@ library Cursors {
1115
1100
  /// @return value Native value assigned to the pipe.
1116
1101
  /// @return account Command account identifier.
1117
1102
  /// @return state Embedded state block stream.
1118
- /// @return request Embedded request block stream.
1103
+ /// @return steps Embedded step block stream.
1119
1104
  function unpackPipe(
1120
1105
  Cur memory cur
1121
- ) internal pure returns (uint value, bytes32 account, bytes calldata state, bytes calldata request) {
1106
+ ) internal pure returns (uint value, bytes32 account, bytes calldata state, bytes calldata steps) {
1122
1107
  uint end = cur.enter(Keys.Pipe, 32 + Sizes.Header + 32 + 2 * Sizes.Header, 0);
1123
1108
  value = uint(cur.read32());
1124
- (account, state, request) = cur.unpackContext();
1109
+ (account, state, steps) = cur.unpackContext();
1110
+ cur.exit(end);
1111
+ }
1112
+
1113
+ /// @notice Consume a RELAY block and return its destination chain, endowment, and step stream.
1114
+ /// @param cur Cursor; advanced past the block.
1115
+ /// @return chain Destination chain node ID.
1116
+ /// @return endowment Native value requested for the destination pipe.
1117
+ /// @return steps Embedded step block stream.
1118
+ function unpackRelay(
1119
+ Cur memory cur
1120
+ ) internal pure returns (uint chain, uint endowment, bytes calldata steps) {
1121
+ uint end = cur.enter(Keys.Relay, 64 + Sizes.Header, 0);
1122
+ chain = uint(cur.read32());
1123
+ endowment = uint(cur.read32());
1124
+ steps = cur.unpackBytes();
1125
+ cur.exit(end);
1126
+ }
1127
+
1128
+ /// @notice Consume a DISPATCH block and return its destination chain, endowment, and payload.
1129
+ /// @param cur Cursor; advanced past the block.
1130
+ /// @return chain Destination chain node ID.
1131
+ /// @return endowment Native value requested for the destination dispatch.
1132
+ /// @return payload Encoded cross-chain payload.
1133
+ function unpackDispatch(
1134
+ Cur memory cur
1135
+ ) internal pure returns (uint chain, uint endowment, bytes calldata payload) {
1136
+ uint end = cur.enter(Keys.Dispatch, 64 + Sizes.Header, 0);
1137
+ chain = uint(cur.read32());
1138
+ endowment = uint(cur.read32());
1139
+ payload = cur.unpackBytes();
1125
1140
  cur.exit(end);
1126
1141
  }
1127
1142
 
@@ -1194,15 +1209,6 @@ library Cursors {
1194
1209
  if (uint(bytes32(msg.data[abs + 64:abs + 96])) != 1) revert UnexpectedValue();
1195
1210
  }
1196
1211
 
1197
- /// @notice Consume a MINIMUM block and assert it matches the expected asset and meta.
1198
- /// @param cur Cursor; advanced past the block.
1199
- /// @param asset Expected asset identifier.
1200
- /// @param meta Expected metadata slot.
1201
- /// @return amount Minimum amount from the block.
1202
- function requireMinimum(Cur memory cur, bytes32 asset, bytes32 meta) internal pure returns (uint amount) {
1203
- return requireAssetAmount(cur, Keys.Minimum, asset, meta);
1204
- }
1205
-
1206
1212
  /// @notice Consume a host amount block and assert it matches the expected host.
1207
1213
  /// @param cur Cursor; advanced past the block.
1208
1214
  /// @param key Expected block type key.
@@ -1324,7 +1330,39 @@ library Cursors {
1324
1330
  }
1325
1331
 
1326
1332
  // -------------------------------------------------------------------------
1327
- // Trailing-block helpers (search after bound)
1333
+ // ensure* - validate constraint blocks against provided values
1334
+ // -------------------------------------------------------------------------
1335
+
1336
+ /// @notice Consume a BALANCE_LIMIT block and assert all constraint fields match the provided balance.
1337
+ /// @param cur Cursor; advanced past the block.
1338
+ /// @param asset Expected asset identifier.
1339
+ /// @param meta Expected metadata slot.
1340
+ /// @param amount Amount that must fall within the encoded min/max range.
1341
+ function ensureBalanceLimit(Cur memory cur, bytes32 asset, bytes32 meta, uint amount) internal pure {
1342
+ uint abs = consume(cur, 0, Keys.BalanceLimit, 128, 128);
1343
+ if (bytes32(msg.data[abs:abs + 32]) != asset) revert UnexpectedValue();
1344
+ if (bytes32(msg.data[abs + 32:abs + 64]) != meta) revert UnexpectedValue();
1345
+ if (uint(bytes32(msg.data[abs + 64:abs + 96])) > amount) revert UnexpectedValue();
1346
+ if (uint(bytes32(msg.data[abs + 96:abs + 128])) < amount) revert UnexpectedValue();
1347
+ }
1348
+
1349
+ /// @notice Consume a CUSTODY_LIMIT block and assert all constraint fields match the provided custody.
1350
+ /// @param cur Cursor; advanced past the block.
1351
+ /// @param host Expected host node ID.
1352
+ /// @param asset Expected asset identifier.
1353
+ /// @param meta Expected metadata slot.
1354
+ /// @param amount Amount that must fall within the encoded min/max range.
1355
+ function ensureCustodyLimit(Cur memory cur, uint host, bytes32 asset, bytes32 meta, uint amount) internal pure {
1356
+ uint abs = consume(cur, 0, Keys.CustodyLimit, 160, 160);
1357
+ if (uint(bytes32(msg.data[abs:abs + 32])) != host) revert UnexpectedValue();
1358
+ if (bytes32(msg.data[abs + 32:abs + 64]) != asset) revert UnexpectedValue();
1359
+ if (bytes32(msg.data[abs + 64:abs + 96]) != meta) revert UnexpectedValue();
1360
+ if (uint(bytes32(msg.data[abs + 96:abs + 128])) > amount) revert UnexpectedValue();
1361
+ if (uint(bytes32(msg.data[abs + 128:abs + 160])) < amount) revert UnexpectedValue();
1362
+ }
1363
+
1364
+ // -------------------------------------------------------------------------
1365
+ // Search helpers
1328
1366
  // -------------------------------------------------------------------------
1329
1367
 
1330
1368
  /// @notice Look for a NODE block anywhere in a calldata source and return its value.
@@ -1364,52 +1402,4 @@ library Cursors {
1364
1402
  (uint abs, ) = expect(cur, i, 0, Keys.Account, 32, 32);
1365
1403
  return bytes32(msg.data[abs:abs + 32]);
1366
1404
  }
1367
-
1368
- /// @notice Look for a NODE block after the current run boundary and return its value.
1369
- /// Searches from `cur.bound` to the end of the source region.
1370
- /// @param cur Source cursor; `bound` marks the end of the primary run.
1371
- /// @param backup Value to return if no NODE block is found.
1372
- /// @return node Node ID from the NODE block, or `backup` if absent.
1373
- function nodeAfter(Cur memory cur, uint backup) internal pure returns (uint node) {
1374
- uint i = find(cur, cur.bound, Keys.Node);
1375
- if (i == cur.len) return backup;
1376
-
1377
- (uint abs, ) = expect(cur, i, 0, Keys.Node, 32, 32);
1378
- return uint(bytes32(msg.data[abs:abs + 32]));
1379
- }
1380
-
1381
- /// @notice Look for an ACCOUNT block after the current run boundary and return its value.
1382
- /// Searches from `cur.bound` to the end of the source region.
1383
- /// @param cur Source cursor; `bound` marks the end of the primary run.
1384
- /// @param backup Account to return if no ACCOUNT block is found.
1385
- /// @return account Account from the ACCOUNT block, or `backup` if absent.
1386
- function accountAfter(Cur memory cur, bytes32 backup) internal pure returns (bytes32 account) {
1387
- uint i = find(cur, cur.bound, Keys.Account);
1388
- if (i == cur.len) return backup;
1389
-
1390
- (uint abs, ) = expect(cur, i, 0, Keys.Account, 32, 32);
1391
- return bytes32(msg.data[abs:abs + 32]);
1392
- }
1393
-
1394
- /// @notice Parse the trailing AUTH block and compute the signed message hash.
1395
- /// The AUTH block must occupy the final `Sizes.Auth` bytes of the source region
1396
- /// and must begin after `cur.bound`.
1397
- /// The signed slice covers from `cur.i` up to (but not including) the AUTH proof bytes.
1398
- /// @param cur Source cursor; `bound` marks the end of the primary data region.
1399
- /// @param cid Command ID that the signature must be bound to.
1400
- /// @return digest keccak256 of the signed message slice.
1401
- /// @return deadline Expiry timestamp from the AUTH block.
1402
- /// @return proof Raw proof bytes (layout: `[bytes20 signer][bytes65 sig]`).
1403
- function authLast(
1404
- Cur memory cur,
1405
- uint cid
1406
- ) internal pure returns (bytes32 digest, uint deadline, bytes calldata proof) {
1407
- if (cur.len - cur.i < Sizes.Auth) revert MalformedBlocks();
1408
-
1409
- uint i = cur.len - Sizes.Auth;
1410
- if (i < cur.bound) revert MalformedBlocks();
1411
-
1412
- (deadline, proof) = expectAuth(cur, i, cid);
1413
- digest = cur.hash(cur.i, cur.len - Sizes.Proof);
1414
- }
1415
1405
  }
package/blocks/Keys.sol CHANGED
@@ -7,20 +7,22 @@ pragma solidity ^0.8.33;
7
7
  library Keys {
8
8
  /// @dev Empty / unset key.
9
9
  bytes4 constant Empty = bytes4(0);
10
+ /// @dev Wildcard key used in discovery when any block stream is accepted.
11
+ bytes4 constant Any = 0xffffffff;
10
12
  /// @dev Input amount - (bytes32 asset, bytes32 meta, uint amount)
11
13
  bytes4 constant Amount = bytes4(keccak256("#amount"));
12
14
  /// @dev Ledger balance - (bytes32 asset, bytes32 meta, uint amount)
13
15
  bytes4 constant Balance = bytes4(keccak256("#balance"));
16
+ /// @dev Balance constraint - (bytes32 asset, bytes32 meta, uint min, uint max)
17
+ bytes4 constant BalanceLimit = bytes4(keccak256("#balanceLimit"));
14
18
  /// @dev Host-scoped request amount - (uint host, bytes32 asset, bytes32 meta, uint amount)
15
19
  bytes4 constant Allocation = bytes4(keccak256("#allocation"));
16
20
  /// @dev Host-scoped allowance cap - (uint host, bytes32 asset, bytes32 meta, uint amount)
17
21
  bytes4 constant Allowance = bytes4(keccak256("#allowance"));
18
22
  /// @dev Cross-host custody state - (uint host, bytes32 asset, bytes32 meta, uint amount)
19
23
  bytes4 constant Custody = bytes4(keccak256("#custody"));
20
- /// @dev Minimum acceptable output - (bytes32 asset, bytes32 meta, uint amount)
21
- bytes4 constant Minimum = bytes4(keccak256("#minimum"));
22
- /// @dev Maximum allowable spend - (bytes32 asset, bytes32 meta, uint amount)
23
- bytes4 constant Maximum = bytes4(keccak256("#maximum"));
24
+ /// @dev Cross-host custody constraint - (uint host, bytes32 asset, bytes32 meta, uint min, uint max)
25
+ bytes4 constant CustodyLimit = bytes4(keccak256("#custodyLimit"));
24
26
  /// @dev Fee amount - (uint amount)
25
27
  bytes4 constant Fee = bytes4(keccak256("#fee"));
26
28
  /// @dev List wrapper; payload is an embedded repeated block stream
@@ -33,12 +35,14 @@ library Keys {
33
35
  bytes4 constant Bytes = bytes4(keccak256("#bytes"));
34
36
  /// @dev Account identifier - (bytes32 account)
35
37
  bytes4 constant Account = bytes4(keccak256("#account"));
36
- /// @dev Transfer payout request - (bytes32 account, bytes32 asset, bytes32 meta, uint amount)
37
- bytes4 constant Payout = bytes4(keccak256("#payout"));
38
38
  /// @dev Transfer record passed through the pipeline - (bytes32 from, bytes32 to, bytes32 asset, bytes32 meta, uint amount)
39
39
  bytes4 constant Transaction = bytes4(keccak256("#transaction"));
40
40
  /// @dev Sub-command invocation - (uint target, uint value, #bytes as request)
41
41
  bytes4 constant Step = bytes4(keccak256("#step"));
42
+ /// @dev Cross-chain pipe relay - (uint chain, uint endowment, #bytes as steps)
43
+ bytes4 constant Relay = bytes4(keccak256("#relay"));
44
+ /// @dev Cross-chain encoded payload dispatch - (uint chain, uint endowment, #bytes as payload)
45
+ bytes4 constant Dispatch = bytes4(keccak256("#dispatch"));
42
46
  /// @dev Raw external call - (uint target, uint value, #bytes as payload)
43
47
  bytes4 constant Call = bytes4(keccak256("#call"));
44
48
  /// @dev Command context transport - (bytes32 account, #bytes as state, #bytes as request)