@rootzero/contracts 1.29.0 → 1.30.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
@@ -8,6 +8,70 @@ sections are immutable and must continue to describe the tagged release.
8
8
 
9
9
  ## Unreleased
10
10
 
11
+ ## 1.30.0
12
+
13
+ ### Breaking Changes
14
+
15
+ - Replaced the obsolete second representation/width byte in every structured
16
+ ID with a trailing flags byte. The type prefix is now
17
+ `[uint8 representation][uint8 category][uint8 subtype][uint8 flags]`, changing
18
+ every structured account, asset, and node ID. Endpoint IDs copy the same flags
19
+ published in their descriptors.
20
+ - Added the canonical handoff command flag. Relay commands now publish command
21
+ IDs carrying `Flags.Handoff`. `Pipeline.pipe` now wraps a flagged STEP's
22
+ ordinary input and untouched continuation in a RELAY block, calls the handoff
23
+ command once, and stops local execution of that continuation.
24
+ The internal RELAY block changed from `(portal, resources, input)` to two
25
+ nested byte streams: command-specific `input` and remaining `steps`. Relay
26
+ hooks now receive `account`, complete `state`, opaque command input, remaining
27
+ steps, and their value budget; each transport implementation decodes its own
28
+ command input.
29
+ - Added `Flags.HandoffFunded` as the canonical combination used by funded
30
+ handoff commands, matching the existing `Flags.AdminFunded` convention.
31
+ - Removed the free `rawCommandCall` API and moved its cursor-aware assembly path
32
+ into `Pipeline` as a private implementation detail. Pipeline's private `run`
33
+ path now owns local execution, trust, command unpacking, handoff selection,
34
+ invocation, and cursor advancement. `pipe` accepts the STEP stream as
35
+ calldata and packs its current position, end, command-input offset, and
36
+ command-input length into one private cursor. The invocation path uses those
37
+ fields directly for ordinary input or to wrap a handoff continuation in a
38
+ RELAY block.
39
+ - Removed the free `unpackCommand` utility. Pipeline now privately validates
40
+ command IDs and extracts the target and direct handoff decision needed for
41
+ routing; the private invoker derives selector and target directly from the ID.
42
+ - Aligned no-argument `raw` access across cursors, decoders, and executions to
43
+ return only the unread region from the current position. `rawState`,
44
+ `rawInput`, `takeRawState`, and `takeRawInput` now follow the same rule;
45
+ explicitly bounded `raw(from, to)` slices remain frame-relative.
46
+
47
+ - Replaced the stateless `RawNodeCalls` inheritance contract with imported
48
+ `tryRawCall`, `rawCall`, and `rawQuery` free functions. Added overloads that
49
+ accept a selector and native target separately from selector-free ABI
50
+ arguments, without allocating a concatenated calldata buffer.
51
+ - Changed `Host`, `CommandHost`, `CommanderAccess`, and `HostIntroduction`
52
+ constructors from an EVM `address` to a protocol-native `uint` commander host
53
+ ID. Nonzero commanders must be valid local host IDs; the EVM address is
54
+ extracted internally for caller checks and introductions, while zero retains
55
+ the self-commanded `Host` convention and remains invalid for `CommandHost`.
56
+ - Removed `RequestAllowanceHook`. `RequestAllowancePort` now reuses the
57
+ authoritative `AllowanceHook` and scopes each allowance update to the
58
+ authenticated peer before calling `allowance(peer, asset, amount)`.
59
+ - Changed `Cashout` from a `#cashout { uint amount }` input command into a
60
+ native-only `#balance` state consumer. Removed the CASHOUT block key, spec,
61
+ schema, codec helpers, and test encoder; cashout now reverts `InvalidAsset`
62
+ for non-native balance state.
63
+ - Reordered `Settlement` hook inheritance to `PostHook`,
64
+ `DebitAccountHook`, `CreditAccountHook`, `SettleHook`, and `RepayHook`.
65
+ Downstream hosts combining `Settlement` with hook-bearing endpoints may need
66
+ to reorder their base contracts.
67
+
68
+ ### Changed
69
+
70
+ - Reduced per-step pipeline overhead by replacing the general lane cursor with
71
+ a private four-field pipeline cursor, specializing STEP advancement around
72
+ decoder-established bounds, preparing handoff input once, and avoiding
73
+ command and flag work on paths that do not need it.
74
+
11
75
  ## 1.29.0
12
76
 
13
77
  ### Breaking Changes
package/Codec.sol CHANGED
@@ -7,7 +7,8 @@ pragma solidity ^0.8.33;
7
7
  import { AssetAmount, AccountAsset, HostAsset, AccountAmount, HostAmount, HostAccountAsset, HostAccountAmount, Debt, Position, Tx } from "./core/Types.sol";
8
8
  import { Keys } from "./codec/Keys.sol";
9
9
  import { Sizes, Specs } from "./codec/Specs.sol";
10
- import { Descriptors, Flags } from "./codec/Descriptors.sol";
10
+ import { Descriptors } from "./codec/Descriptors.sol";
11
+ import { Flags } from "./utils/Flags.sol";
11
12
  import { Schemas } from "./codec/Schema.sol";
12
13
  import { Decoders } from "./codec/Decoders.sol";
13
14
  import { Cursors, Cur } from "./utils/Cursors.sol";
package/Commands.sol CHANGED
@@ -5,7 +5,7 @@ pragma solidity ^0.8.33;
5
5
  // Import this file for both standard Execution-based commands and custom decoders.
6
6
 
7
7
  import {CommandBase} from "./commands/Base.sol";
8
- import {Flags} from "./codec/Descriptors.sol";
8
+ import {Flags} from "./utils/Flags.sol";
9
9
  import {Execution, Executions} from "./execution/Execution.sol";
10
10
  import {Lanes} from "./utils/Lanes.sol";
11
11
  import {Blocks} from "./codec/Blocks.sol";
package/Core.sol CHANGED
@@ -12,7 +12,7 @@ import { Balances, InsufficientFunds } from "./core/Balances.sol";
12
12
  import { Escrows, InsufficientEscrow } from "./core/Escrows.sol";
13
13
  import { NativeAsset, Runtime } from "./core/Runtime.sol";
14
14
  import { Admins, CommandHost, Guardians, Host, HostIntroduction, IHostIntroduction } from "./core/Host.sol";
15
- import { FailedCall, NodeCalls, PortCalls, RawNodeCalls, rawCommandCall } from "./core/Calls.sol";
15
+ import { FailedCall, NodeCalls, PortCalls, rawCall, rawQuery, tryRawCall } from "./core/Calls.sol";
16
16
  import { EndpointBase, InputEndpointBase } from "./core/Endpoint.sol";
17
17
  import { ExecuteHook, PipeHook, Pipeline } from "./core/Pipeline.sol";
18
18
  import { Budget, Budgets } from "./execution/Budget.sol";
package/Endpoints.sol CHANGED
@@ -5,7 +5,7 @@ pragma solidity ^0.8.33;
5
5
  // Import this file to inherit from the full rootzero callable host surface without managing individual paths.
6
6
 
7
7
  // Shared endpoint hooks
8
- import {Flags} from "./codec/Descriptors.sol";
8
+ import {Flags} from "./utils/Flags.sol";
9
9
  import {ExecuteHook, PipeHook} from "./core/Pipeline.sol";
10
10
  import {CreditAccountHook, DebitAccountHook, PostHook, RepayHook, SettleHook} from "./core/Settlement.sol";
11
11
 
@@ -48,7 +48,7 @@ import {Unauthorize} from "./commands/admin/Unauthorize.sol";
48
48
  // Port endpoints
49
49
  import {PortBase} from "./ports/Base.sol";
50
50
  import {AllowAssetsPort, DenyAssetsPort, RequestAssetPort, RequestAssetHook} from "./ports/Assets.sol";
51
- import {RequestAllowancePort, RequestAllowanceHook} from "./ports/Allowance.sol";
51
+ import {RequestAllowancePort} from "./ports/Allowance.sol";
52
52
  import {RedeemBalancePort, RedeemBalanceHook} from "./ports/Redeem.sol";
53
53
  import {CreditAccountPort} from "./ports/Credit.sol";
54
54
  import {DebitAccountPort} from "./ports/Debit.sol";
package/README.md CHANGED
@@ -38,7 +38,7 @@ import { CommandHost, Balances } from "@rootzero/contracts/Core.sol";
38
38
  import { Deposit } from "@rootzero/contracts/Endpoints.sol";
39
39
 
40
40
  contract ExampleHost is CommandHost, Balances, Deposit {
41
- constructor(address commander) CommandHost(commander) {}
41
+ constructor(uint commander) CommandHost(commander) {}
42
42
 
43
43
  function deposit(bytes32 account, bytes32 asset, uint amount) internal override {
44
44
  uint balance = creditTo(account, asset, amount);
@@ -47,17 +47,17 @@ contract ExampleHost is CommandHost, Balances, Deposit {
47
47
  }
48
48
  ```
49
49
 
50
- `CommandHost` requires a nonzero commander and accepts command calls only from
51
- that address. It has no built-in admin commands, peer registry, guardians,
50
+ `CommandHost` requires a nonzero local commander host ID and accepts command
51
+ calls only from its embedded native caller. It has no built-in admin commands, peer registry, guardians,
52
52
  inbound introduction endpoint, generic execution command, or native-token
53
53
  receive function. Use `Host` instead when the application needs those advanced
54
54
  facilities; its commands accept the commander, the host itself, and explicitly
55
55
  authorized host callers.
56
56
 
57
- Both host types introduce themselves during deployment when the commander is a
58
- contract. That commander must implement `introduce(uint,uint)` and accept the
59
- call, otherwise deployment reverts. EOA commanders do not receive an
60
- introduction call.
57
+ Both host types introduce themselves during deployment when the native target
58
+ encoded by the commander host ID is a contract. That commander must implement
59
+ `introduce(uint,uint)` and accept the call, otherwise deployment reverts. Host
60
+ IDs encoding EOAs do not receive an introduction call.
61
61
 
62
62
  Host contracts are designed for fresh deployment rather than proxy upgrades.
63
63
  Releases may change inheritance storage layout, immutable configuration, and
@@ -68,16 +68,18 @@ Commands using trusted outbound `NodeCalls` also require a `TrustAccess`
68
68
  implementation. The advanced `Host` supplies one through its composed node access, while
69
69
  `CommandHost` deliberately does not. A minimal host can explicitly compose a
70
70
  custom trust policy, or a command that intentionally targets arbitrary nodes
71
- can inherit `RawNodeCalls` instead.
71
+ can import the free raw-call helpers instead.
72
72
 
73
- Deploy it with your own address as commander and you can call its commands
73
+ Deploy it with the local host ID encoding your native identity as commander and you can call its commands
74
74
  directly. A input is a run of binary blocks — here, a single `#amount` block
75
75
  asking to deposit an asset (the encoders are a few lines each; see
76
+ [`test/helpers/setup.ts`](test/helpers/setup.ts) and
76
77
  [`test/helpers/blocks.ts`](test/helpers/blocks.ts) for reference
77
78
  implementations):
78
79
 
79
80
  ```ts
80
- const host = await ethers.deployContract("ExampleHost", [deployer.address]);
81
+ const commander = await hostId(deployer.address);
82
+ const host = await ethers.deployContract("ExampleHost", [commander]);
81
83
 
82
84
  const account = encodeUserAccount(user.address); // receiving account
83
85
  const input = encodeAmountBlock(asset, 100n); // what to deposit
@@ -188,7 +190,8 @@ Structured EVM IDs use:
188
190
  [uint32 type][uint32 chainid][192-bit payload]
189
191
  ```
190
192
 
191
- where `type` packs `[uint16 representation][uint8 category][uint8 subtype]`. A
193
+ where `type` packs
194
+ `[uint8 representation][uint8 category][uint8 subtype][uint8 flags]`. A
192
195
  structured ID announces what it is (an account, an asset, a node) and which
193
196
  chain it lives on, and the payload usually embeds the underlying address. User
194
197
  accounts are chain-agnostic, while admin accounts are chain-local. Guardians
@@ -229,8 +232,10 @@ which accepts `hostAsset { uint host, bytes32 asset }` entries and always applie
229
232
  a zero allowance. The full `Host` composes
230
233
  both, while smaller hosts can inherit either bundle separately.
231
234
 
232
- Trust is explicit and minimal. Each host has an immutable **commander**
233
- address fixed at construction, from which its **admin account** is derived.
235
+ Trust is explicit and minimal. Each host receives an immutable **commander host
236
+ ID** at construction. The EVM port validates that it is local, extracts its
237
+ native address internally for caller checks, and derives the **admin account**
238
+ from that native identity.
234
239
  Other contracts become callers only when their node ID is authorized into the
235
240
  host's trusted set, and **guardians** are accounts allowed to take protective
236
241
  actions. At deployment, a host introduces itself to its commander, which is how
@@ -341,13 +346,16 @@ abstract contract MyCommand is CommandBase {
341
346
 
342
347
  The final argument is a packed flags byte. Pass `0` for an ordinary endpoint,
343
348
  or compose values such as `Flags.Funded`, `Flags.Admin`, and
344
- `Flags.AdminFunded` from the command or endpoint package entry point. Bits 6
345
- and 7 are reserved for endpoint-defined custom flags. Bits 2 through 5 remain
346
- reserved for future protocol flags.
349
+ `Flags.AdminFunded` from the command or endpoint package entry point.
350
+ The same flags byte is copied into the endpoint ID, keeping runtime behavior and
351
+ published descriptor metadata aligned. `Flags.Handoff` marks a command that
352
+ takes ownership of the remaining pipeline, while `Flags.HandoffFunded` combines
353
+ handoff behavior with native-value funding;
354
+ bit 6 remains endpoint-defined, and bits 2 through 5 remain reserved.
347
355
 
348
356
  The standard commands cover the common ledger movements: `bootstrap` (source
349
- an initial balance and native-value budget), `cashout` (withdraw a requested
350
- native-asset amount), `deposit` and
357
+ an initial balance and native-value budget), `cashout` (withdraw native
358
+ `#balance` state), `deposit` and
351
359
  `depositPayable` (external funds in), `settlePayable` (funded settlement),
352
360
  `withdraw` and `burn` (funds out),
353
361
  `debitAccount` and `creditAccount` (internal movements), `payout` (deliver
@@ -383,23 +391,18 @@ metadata but is only executable through local pipeline execution. This is the co
383
391
  `Pipeline.pipe`:
384
392
 
385
393
  ```solidity
386
- while (cur.more()) {
387
- (uint cmd, uint value, bytes calldata input) = cur.unpackStep();
388
- (bytes4 selector, address target) = unpackCommand(cmd);
389
- if (budget < value) revert InsufficientValue();
394
+ uint cursor;
395
+ assembly ("memory-safe") {
396
+ cursor := or(steps.offset, shl(32, add(steps.offset, steps.length)))
397
+ }
398
+ while (uint32(cursor) < uint32(cursor >> 32)) {
399
+ uint cmd;
400
+ uint value;
401
+ (cmd, value, cursor) = takeStep(cursor);
402
+ if (value > budget) revert InsufficientValue();
390
403
  unchecked { budget -= value; }
391
- bool handled;
392
- bytes memory output;
393
- uint credit;
394
- if (target == address(this)) {
395
- (handled, output, credit) = execute(cmd, account, state, input, value);
396
- }
397
- if (!handled) {
398
- ensureTrusted(cmd);
399
- (output, credit) = rawCommandCall(selector, target, value, account, state, input);
400
- }
401
- state = output;
402
- budget += credit;
404
+ (state, value, cursor) = run(cmd, account, state, value, cursor);
405
+ budget += value;
403
406
  }
404
407
  if (state.length != 0) revert UnexpectedState();
405
408
  ```
@@ -408,6 +411,42 @@ if (state.length != 0) revert UnexpectedState();
408
411
  the remaining budget after every step has executed. The enclosing entrypoint
409
412
  settles that final value once.
410
413
 
414
+ The EVM pipeline is deliberately coupled to the canonical wire layout for gas
415
+ efficiency. It extracts command selectors, targets, and flags directly from
416
+ command IDs and writes CONTEXT, BYTES, and RELAY blocks directly in assembly.
417
+ Any change to those ID fields or block encodings must update `Pipeline` at the
418
+ same time; the general node and block helpers are not used on this hot path.
419
+
420
+ A handoff command retains the ordinary command subtype and carries
421
+ `Flags.Handoff` in both its ID and descriptor. The reserved handoff envelope is:
422
+
423
+ ```txt
424
+ relay { #bytes as input, #bytes as steps }
425
+ ```
426
+
427
+ `input` is the handoff STEP's ordinary command input, while `steps` is the
428
+ untouched remainder of the original pipeline. `Pipeline.pipe` constructs this
429
+ envelope automatically for commands carrying `Flags.Handoff`, calls the command,
430
+ and stops executing the transferred continuation locally. Pipeline authors
431
+ therefore encode only the command's ordinary input in the handoff STEP.
432
+
433
+ Handoff has four operational rules:
434
+
435
+ - A host-local `execute` hook must return `handled = false` for handoff command
436
+ IDs. The hook sees only ordinary STEP input; delegation lets `Pipeline`
437
+ construct the RELAY envelope containing the continuation.
438
+ - Handoff transfers STEP bytes, not the pipeline's complete native-value
439
+ budget. The handoff command receives its assigned STEP value. Returned credit
440
+ rejoins the source budget, and the enclosing source entrypoint settles the
441
+ final remainder normally.
442
+ - The handoff command must consume or forward the current state and return empty
443
+ state. Because the continuation is no longer executed locally, returned
444
+ non-empty state fails pipeline finalization with `UnexpectedState`.
445
+ - A transport that completes asynchronously should relinquish only state that
446
+ is safe before destination success is known. The standard relay commands are
447
+ intended for empty or balance state; debt, position, and similarly fallible
448
+ state must not be relayed this way.
449
+
411
450
  A transfer, for instance, is a two-step pipeline: `debitAccount` turns an
412
451
  `#amount` input into `#balance` state, and `payout` consumes that state
413
452
  toward a recipient. Because a pipeline is just blocks, it is also the unit of
@@ -423,9 +462,9 @@ Hosts that implement a pipeline locally can inherit `Bootstrap`,
423
462
  `ExecuteRepay` to register canonical command metadata while executing
424
463
  their local command IDs through `executeBootstrap`, `executeCashout`,
425
464
  `executeDebitAccount`, `executeCreditAccount`, `executeSettle`, and
426
- `executeRepay`. The bootstrap, cashout, and debit adapters decode fixed-stride
427
- calldata input directly; the other three
428
- decode memory-backed pipeline state. All return `handled = true` and avoid an
465
+ `executeRepay`. The bootstrap and debit adapters decode fixed-stride calldata
466
+ input directly; cashout, credit, settle, and repay decode memory-backed pipeline
467
+ state. All return `handled = true` and avoid an
429
468
  external self-call. The host's `execute` hook must authorize a command before
430
469
  returning true. Returning `handled = false` delegates a local command to its
431
470
  trusted normal external entrypoint. Pass the step value into each adapter.
@@ -467,8 +506,14 @@ the descriptor's lanes through the published block schemas.
467
506
 
468
507
  ## Ports
469
508
 
470
- Ports are the host-to-host surfaces, callable only by trusted peer hosts. The two
471
- central ones are batches all the way down:
509
+ Ports are the host-to-host surfaces, callable only by trusted peer hosts. By
510
+ default, trusting a peer means the host has verified that peer and permits it to
511
+ use the functionality of every port the host exposes without another policy
512
+ decision inside each port. A host that needs narrower capabilities can add
513
+ per-port or per-operation checks in its hook implementations, or expose custom
514
+ ports with a different access model.
515
+
516
+ The central ports are batches all the way down:
472
517
 
473
518
  - `portPost` consumes `transaction { bytes32 from, bytes32 to, bytes32 asset,
474
519
  uint amount }` blocks, debiting `from` and crediting `to` per
@@ -477,14 +522,16 @@ central ones are batches all the way down:
477
522
  passes the authenticated peer, asset, and amount to a host hook. The hook
478
523
  validates asset support and applies the host's request and transfer policy.
479
524
  - `portRequestAllowance` consumes the same amount blocks and lets the
480
- authenticated peer request an asset allowance. The hook decides what
481
- allowance, if any, to grant.
525
+ authenticated peer set its own asset allowance through the same authoritative
526
+ hook used by the admin allowance command.
482
527
  - `portPipePayable` consumes `context` blocks, each carrying an account, an
483
528
  initial state, and a run of steps — a complete pipeline delivered by another
484
529
  host, executed locally against the port call's shared value budget.
485
530
 
486
- This is also the cross-portal mechanism. `relayPayable`, `relayBalancePayable`,
487
- or `portDispatchPayable` wraps a pipe and addresses it to a portal, commonly the destination host ID;
531
+ This is also the cross-portal mechanism. `relayPayable` and
532
+ `relayBalancePayable` are handoff commands whose transport hooks decode their
533
+ own destination and resource input, while `portDispatchPayable` dispatches an
534
+ explicit portal payload. The adapter wraps and addresses the destination pipe;
488
535
  a bridge adapter moves the **raw
489
536
  bytes**; the destination host parses them with the same cursor rules and runs
490
537
  the same pipeline loop. Nothing in the payload is EVM-specific — step commands
package/Utils.sol CHANGED
@@ -10,8 +10,9 @@ import { Actions } from "./utils/Actions.sol";
10
10
  import { Amounts, Assets } from "./utils/Assets.sol";
11
11
  import { ECDSA } from "./utils/ECDSA.sol";
12
12
  import { Ids } from "./utils/Ids.sol";
13
- import { Nodes, unpackCommand } from "./utils/Nodes.sol";
13
+ import { Nodes } from "./utils/Nodes.sol";
14
14
  import { Layout } from "./utils/Layout.sol";
15
+ import { Flags } from "./utils/Flags.sol";
15
16
  import { BadAmount, InsufficientValue, InvalidAccount, InvalidAsset, InvalidContract, InvalidId, InvalidPreimage, MissingCursor, NotDivisible, OutOfBounds, UnauthorizedAsset, UnconsumedData, UnexpectedPosition, ValueOverflow, ZeroAddress, ZeroAmount} from "./utils/Errors.sol";
16
17
  import { addrOr, applyBps, beforeBps, bytes32ToInt, bytes32ToString, clear8, clear16, clear32, clear64, divisible, ensureAddr, ensureContract, hash32, intToBytes32, isFamily, matchesBase, MAX_BPS, max8, max16, max24, max32, max40, max64, max96, max128, max160, replace8, replace16, replace32, replace64, retryTicket, toLocalBase, toUnspecifiedBase } from "./utils/Utils.sol";
17
18
 
package/codec/Blocks.sol CHANGED
@@ -380,20 +380,6 @@ library Blocks {
380
380
  }
381
381
  }
382
382
 
383
- /// @notice Write a CASHOUT block at `i`.
384
- /// @dev DANGER: Unchecked memory write. Reserve `Sizes.Cashout` bytes first.
385
- /// @param dst Destination buffer.
386
- /// @param i Relative write position.
387
- /// @param amount Native-asset amount to withdraw.
388
- function writeCashout(bytes memory dst, uint i, uint amount) internal pure {
389
- uint spec = Specs.Cashout;
390
- assembly ("memory-safe") {
391
- let p := add(add(dst, 0x20), i)
392
- mstore(p, spec)
393
- mstore(add(p, 0x08), amount)
394
- }
395
- }
396
-
397
383
  // Two-word payloads
398
384
 
399
385
  /// @notice Write an AMOUNT block at `i`.
@@ -808,23 +794,23 @@ library Blocks {
808
794
  /// block size and ensure the encoded payload length fits in uint32.
809
795
  /// @param dst Destination buffer.
810
796
  /// @param i Relative write position.
811
- /// @param portal Destination portal.
812
- /// @param resources Packed resources.
813
- /// @param input Relay input.
814
- function writeRelay(bytes memory dst, uint i, uint portal, uint resources, bytes memory input) internal pure {
815
- uint len = 64 + Sizes.Header + input.length;
797
+ /// @param input Command-specific handoff input.
798
+ /// @param steps Remaining pipeline steps.
799
+ function writeRelay(bytes memory dst, uint i, bytes memory input, bytes memory steps) internal pure {
800
+ uint len = 2 * Sizes.Header + input.length + steps.length;
816
801
  uint key = uint32(Keys.Relay);
817
802
  uint byteskey = uint32(Keys.Bytes);
818
803
  assembly ("memory-safe") {
819
804
  let p := add(add(dst, 0x20), i)
820
805
  mstore(p, or(shl(224, key), shl(192, len)))
821
- mstore(add(p, 0x08), portal)
822
- mstore(add(p, 0x28), resources)
823
-
824
- let q := add(p, 0x48)
806
+ let q := add(p, 0x08)
825
807
  let inputlen := mload(input)
826
808
  mstore(q, or(shl(224, byteskey), shl(192, inputlen)))
827
809
  mcopy(add(q, 0x08), add(input, 0x20), inputlen)
810
+ q := add(add(q, 0x08), inputlen)
811
+ let stepslen := mload(steps)
812
+ mstore(q, or(shl(224, byteskey), shl(192, stepslen)))
813
+ mcopy(add(q, 0x08), add(steps, 0x20), stepslen)
828
814
  }
829
815
  }
830
816
 
@@ -1053,9 +1039,23 @@ library Blocks {
1053
1039
  copyComposite(dst, i, Keys.Call, target, resources, payload);
1054
1040
  }
1055
1041
 
1056
- /// @notice Encode a RELAY block at `i`, copying its nested input from calldata.
1057
- function copyRelay(bytes memory dst, uint i, uint portal, uint resources, bytes calldata input) internal pure {
1058
- copyComposite(dst, i, Keys.Relay, portal, resources, input);
1042
+ /// @notice Encode a RELAY block at `i`, copying its nested streams from calldata.
1043
+ function copyRelay(bytes memory dst, uint i, bytes calldata input, bytes calldata steps) internal pure {
1044
+ uint len = 2 * Sizes.Header + input.length + steps.length;
1045
+ uint key = uint32(Keys.Relay);
1046
+ uint byteskey = uint32(Keys.Bytes);
1047
+ assembly ("memory-safe") {
1048
+ let p := add(add(dst, 0x20), i)
1049
+ mstore(p, or(shl(224, key), shl(192, len)))
1050
+ let q := add(p, 0x08)
1051
+ let inputlen := input.length
1052
+ mstore(q, or(shl(224, byteskey), shl(192, inputlen)))
1053
+ calldatacopy(add(q, 0x08), input.offset, inputlen)
1054
+ q := add(add(q, 0x08), inputlen)
1055
+ let stepslen := steps.length
1056
+ mstore(q, or(shl(224, byteskey), shl(192, stepslen)))
1057
+ calldatacopy(add(q, 0x08), steps.offset, stepslen)
1058
+ }
1059
1059
  }
1060
1060
 
1061
1061
  /// @notice Encode a DISPATCH block at `i`, copying its nested payload from calldata.
@@ -1458,18 +1458,6 @@ library Blocks {
1458
1458
  if (head >> 192 != Specs.Status >> 192) revert InvalidBlock();
1459
1459
  }
1460
1460
 
1461
- /// @notice Decode a low-level fixed-width CASHOUT block at `abs`.
1462
- /// @param abs Absolute block position.
1463
- /// @return amount Native-asset amount to withdraw.
1464
- function unpackCashout(uint abs) internal pure returns (uint amount) {
1465
- uint head;
1466
- assembly ("memory-safe") {
1467
- head := calldataload(abs)
1468
- amount := calldataload(add(abs, 0x08))
1469
- }
1470
- if (head >> 192 != Specs.Cashout >> 192) revert InvalidBlock();
1471
- }
1472
-
1473
1461
  // Two-word payloads
1474
1462
 
1475
1463
  /// @notice Decode a low-level fixed-width AMOUNT block at `abs`.
@@ -1866,22 +1854,18 @@ library Blocks {
1866
1854
  if (end != limit) revert InvalidBlock();
1867
1855
  }
1868
1856
 
1869
- /// @notice Decode one RELAY block and its nested input.
1857
+ /// @notice Decode one RELAY block and its nested input and continuation.
1870
1858
  /// @param abs Absolute block position.
1871
- /// @return portal Decoded destination portal.
1872
- /// @return resources Decoded packed resources.
1873
- /// @return input Decoded relay input.
1859
+ /// @return input Decoded command-specific input.
1860
+ /// @return steps Decoded remaining pipeline steps.
1874
1861
  /// @return end Absolute position after the block.
1875
1862
  function unpackRelay(
1876
1863
  uint abs
1877
- ) internal pure returns (uint portal, uint resources, bytes calldata input, uint end) {
1864
+ ) internal pure returns (bytes calldata input, bytes calldata steps, uint end) {
1878
1865
  uint limit;
1879
1866
  (abs, limit) = expectKey(abs, Keys.Relay);
1880
- assembly ("memory-safe") {
1881
- portal := calldataload(abs)
1882
- resources := calldataload(add(abs, 0x20))
1883
- }
1884
- (input, end) = unpackBytes(abs + 64);
1867
+ (input, abs) = unpackBytes(abs);
1868
+ (steps, end) = unpackBytes(abs);
1885
1869
  if (end != limit) revert InvalidBlock();
1886
1870
  }
1887
1871
 
@@ -2119,14 +2103,6 @@ library Blocks {
2119
2103
  writeBootstrap(value, 0, asset, amount, budget);
2120
2104
  }
2121
2105
 
2122
- /// @notice Encode a CASHOUT block.
2123
- /// @param amount Native-asset amount to withdraw.
2124
- /// @return value Encoded CASHOUT block bytes.
2125
- function createCashout(uint amount) internal pure returns (bytes memory value) {
2126
- value = allocate(Sizes.Cashout);
2127
- writeCashout(value, 0, amount);
2128
- }
2129
-
2130
2106
  /// @notice Encode an AMOUNT block.
2131
2107
  /// @param asset Asset identifier.
2132
2108
  /// @param amount Token amount.
@@ -2235,22 +2211,20 @@ library Blocks {
2235
2211
  }
2236
2212
 
2237
2213
  /// @notice Encode a RELAY block.
2238
- /// @param portal Destination portal implementation's host ID, passed through
2239
- /// without semantic validation.
2240
- /// @param resources Chain-specific resources for the destination context.
2241
- /// @param input Nested input block stream.
2214
+ /// @param input Nested command-specific input block stream.
2215
+ /// @param steps Nested remaining STEP block stream.
2242
2216
  /// @return value Encoded RELAY block bytes.
2243
- function createRelay(uint portal, uint resources, bytes memory input) internal pure returns (bytes memory value) {
2244
- uint len = max32(Sizes.B64 + Sizes.Header + input.length);
2217
+ function createRelay(bytes memory input, bytes memory steps) internal pure returns (bytes memory value) {
2218
+ uint len = max32(3 * Sizes.Header + input.length + steps.length);
2245
2219
  value = allocate(len);
2246
- writeRelay(value, 0, portal, resources, input);
2220
+ writeRelay(value, 0, input, steps);
2247
2221
  }
2248
2222
 
2249
- /// @notice Encode a RELAY block by copying its nested input from calldata.
2250
- function createRelayCopy(uint portal, uint resources, bytes calldata input) internal pure returns (bytes memory value) {
2251
- uint len = max32(Sizes.B64 + Sizes.Header + input.length);
2223
+ /// @notice Encode a RELAY block by copying its nested streams from calldata.
2224
+ function createRelayCopy(bytes calldata input, bytes calldata steps) internal pure returns (bytes memory value) {
2225
+ uint len = max32(3 * Sizes.Header + input.length + steps.length);
2252
2226
  value = allocate(len);
2253
- copyRelay(value, 0, portal, resources, input);
2227
+ copyRelay(value, 0, input, steps);
2254
2228
  }
2255
2229
 
2256
2230
  /// @notice Encode a DISPATCH block.
@@ -22,7 +22,7 @@ library Decoders {
22
22
  /// @param source Calldata region to open.
23
23
  /// @return cur Cursor spanning the complete source.
24
24
  function open(bytes calldata source) internal pure returns (Cur memory cur) {
25
- cur.state = Cursors.wrap(source, 0);
25
+ cur.state = Cursors.wrap(source);
26
26
  }
27
27
 
28
28
  /// @notice Return whether `cur` has unread bytes.
@@ -138,13 +138,15 @@ library Decoders {
138
138
  out.state = cur.state.slice(from, to, 0);
139
139
  }
140
140
 
141
- /// @notice Return the complete calldata region represented by `cur`.
141
+ /// @notice Return the unread calldata region represented by `cur`.
142
142
  /// @param cur Cursor whose calldata is returned.
143
- /// @return Complete cursor region.
144
- function raw(Cur memory cur) internal pure returns (bytes calldata) {
145
- (uint abs, uint end) = cur.state.bounds();
146
- if (end > msg.data.length) revert Blocks.MalformedBlocks();
147
- return msg.data[abs:end];
143
+ /// @return data Unread cursor region from its current position.
144
+ function raw(Cur memory cur) internal pure returns (bytes calldata data) {
145
+ (uint i, uint offset, uint len) = cur.state.decode();
146
+ if (len > msg.data.length || offset > msg.data.length - len) {
147
+ revert Blocks.MalformedBlocks();
148
+ }
149
+ data = msg.data[offset + i:offset + len];
148
150
  }
149
151
 
150
152
  /// @notice Return relative calldata range `[from, to)` from `cur`.
@@ -376,15 +378,6 @@ library Decoders {
376
378
  node = Blocks.unpackNode(abs);
377
379
  }
378
380
 
379
- /// @notice Decode and consume one CASHOUT block.
380
- /// @param cur Cursor advanced past the block.
381
- /// @return amount Native-asset amount to withdraw.
382
- function unpackCashout(Cur memory cur) internal pure returns (uint amount) {
383
- uint abs;
384
- (cur.state, abs) = cur.state.consume(Sizes.Cashout);
385
- amount = Blocks.unpackCashout(abs);
386
- }
387
-
388
381
  /// @notice Decode and consume one BOOTSTRAP block.
389
382
  /// @param cur Cursor advanced past the block.
390
383
  /// @return asset Decoded asset identifier.
@@ -749,15 +742,14 @@ library Decoders {
749
742
 
750
743
  /// @notice Decode and consume one RELAY block.
751
744
  /// @param cur Cursor advanced past the block.
752
- /// @return portal Decoded destination portal.
753
- /// @return resources Decoded packed resources.
754
745
  /// @return input Decoded relay input.
746
+ /// @return steps Decoded remaining pipeline steps.
755
747
  function unpackRelay(
756
748
  Cur memory cur
757
- ) internal pure returns (uint portal, uint resources, bytes calldata input) {
749
+ ) internal pure returns (bytes calldata input, bytes calldata steps) {
758
750
  uint abs = cur.state.absolute();
759
751
  uint end;
760
- (portal, resources, input, end) = Blocks.unpackRelay(abs);
752
+ (input, steps, end) = Blocks.unpackRelay(abs);
761
753
  cur.state = cur.state.seekAbs(end);
762
754
  }
763
755
 
@@ -5,18 +5,7 @@ import {Blocks} from "./Blocks.sol";
5
5
  import {Buffers} from "./Buffers.sol";
6
6
  import {Sizes, Specs} from "./Specs.sol";
7
7
  import {Lanes} from "../utils/Lanes.sol";
8
-
9
- /// @title Flags
10
- /// @notice Packed endpoint behavior flags.
11
- /// @dev Bits 6 and 7 are reserved for endpoint-defined custom flags.
12
- library Flags {
13
- /// @dev Endpoint accepts nonzero native value.
14
- uint8 internal constant Funded = 1 << 0;
15
- /// @dev Endpoint is restricted to the admin account.
16
- uint8 internal constant Admin = 1 << 1;
17
- /// @dev Endpoint accepts nonzero native value and is restricted to the admin account.
18
- uint8 internal constant AdminFunded = Admin | Funded;
19
- }
8
+ import {Flags} from "../utils/Flags.sol";
20
9
 
21
10
  /// @title Descriptors
22
11
  /// @notice Packing and lane metadata helpers for endpoint descriptors.
@@ -26,8 +15,8 @@ library Descriptors {
26
15
  /// `[input key:4][stride:1]`
27
16
  /// `[output key:4][min:4][max:4][hint:3][stride:1]`
28
17
  /// `[reserved:5]`
29
- /// `[flags:1]`. Flag bits: funded = 0, admin = 1; bits 6 and 7 are
30
- /// reserved for endpoint-defined custom flags.
18
+ /// `[flags:1]`. Flag bits: funded = 0, admin = 1, handoff = 7;
19
+ /// bit 6 is reserved for endpoint-defined custom behavior.
31
20
  /// @dev TODO: Add per-lane cardinality metadata so off-chain consumers can
32
21
  /// distinguish exactly-one lanes from batches. Command decoding must remain
33
22
  /// the runtime source of truth; this metadata should not restore eager scans.