@rootzero/contracts 1.28.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,83 @@ 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
+
75
+ ## 1.29.0
76
+
77
+ ### Breaking Changes
78
+
79
+ - `ExecuteHook.execute` and the optimized local command helpers now return a
80
+ leading `bool handled`. A local command returned as unhandled is invoked
81
+ through its trusted normal external command entrypoint; handled commands keep
82
+ the optimized internal path and are authorized by the hook implementation.
83
+ - Renamed `CashoutInternal`, `DebitAccountInternal`, `CreditAccountInternal`,
84
+ `SettleInternal`, and `RepayInternal` to `ExecuteCashout`,
85
+ `ExecuteDebitAccount`, `ExecuteCreditAccount`, `ExecuteSettle`, and
86
+ `ExecuteRepay`.
87
+
11
88
  ## 1.28.0
12
89
 
13
90
  ### 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
 
@@ -14,23 +14,23 @@ import {CommandBase} from "./commands/Base.sol";
14
14
  import {Allocate, AllocateHook} from "./commands/Allocate.sol";
15
15
  import {Burn, BurnHook} from "./commands/Burn.sol";
16
16
  import {Bootstrap} from "./commands/Bootstrap.sol";
17
- import {Cashout, CashoutHook, CashoutInternal} from "./commands/Cashout.sol";
18
- import {CreditAccount, CreditAccountInternal} from "./commands/Credit.sol";
19
- import {DebitAccount, DebitAccountInternal} from "./commands/Debit.sol";
17
+ import {Cashout, CashoutHook, ExecuteCashout} from "./commands/Cashout.sol";
18
+ import {CreditAccount, ExecuteCreditAccount} from "./commands/Credit.sol";
19
+ import {DebitAccount, ExecuteDebitAccount} from "./commands/Debit.sol";
20
20
  import {Deposit, DepositHook, DepositPayable, DepositPayableHook} from "./commands/Deposit.sol";
21
21
  import {Payout, PayoutHook} from "./commands/Payout.sol";
22
22
  import {Provision, ProvisionHook, ProvisionPayable, ProvisionPayableHook} from "./commands/Provision.sol";
23
23
  import {RecoverPayable, RecoverPayableHook} from "./commands/Recover.sol";
24
24
  import {
25
25
  Repay,
26
- RepayInternal,
26
+ ExecuteRepay,
27
27
  RepayPayable,
28
28
  RepayPayableHook,
29
29
  RepayPosition,
30
30
  RepayPositionPayable
31
31
  } from "./commands/Repay.sol";
32
32
  import {RelayPayable, RelayBalancePayable, RelayPayableHook} from "./commands/Relay.sol";
33
- import {Settle, SettlePayable, SettlePayableHook, SettleInternal} from "./commands/Settle.sol";
33
+ import {Settle, SettlePayable, SettlePayableHook, ExecuteSettle} from "./commands/Settle.sol";
34
34
  import {Withdraw, WithdrawHook} from "./commands/Withdraw.sol";
35
35
 
36
36
  // Admin commands
@@ -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,19 +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
- uint credit;
392
- if (target == address(this)) {
393
- (state, credit) = execute(cmd, account, state, input, value);
394
- } else {
395
- ensureTrusted(cmd);
396
- (state, credit) = rawCommandCall(selector, target, value, account, state, input);
397
- }
398
- budget += credit;
404
+ (state, value, cursor) = run(cmd, account, state, value, cursor);
405
+ budget += value;
399
406
  }
400
407
  if (state.length != 0) revert UnexpectedState();
401
408
  ```
@@ -404,6 +411,42 @@ if (state.length != 0) revert UnexpectedState();
404
411
  the remaining budget after every step has executed. The enclosing entrypoint
405
412
  settles that final value once.
406
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
+
407
450
  A transfer, for instance, is a two-step pipeline: `debitAccount` turns an
408
451
  `#amount` input into `#balance` state, and `payout` consumes that state
409
452
  toward a recipient. Because a pipeline is just blocks, it is also the unit of
@@ -414,15 +457,18 @@ parameters. A `resources` word is never itself native value; EVM adapters use
414
457
  `useResourceValue` to extract its low 128-bit value lane before spending it.
415
458
 
416
459
  Hosts that implement a pipeline locally can inherit `Bootstrap`,
417
- `CashoutInternal`, `DebitAccountInternal`, `CreditAccountInternal`,
418
- `SettleInternal`, and
419
- `RepayInternal` to register canonical command metadata while routing
460
+ `ExecuteCashout`, `ExecuteDebitAccount`, `ExecuteCreditAccount`,
461
+ `ExecuteSettle`, and
462
+ `ExecuteRepay` to register canonical command metadata while executing
420
463
  their local command IDs through `executeBootstrap`, `executeCashout`,
421
464
  `executeDebitAccount`, `executeCreditAccount`, `executeSettle`, and
422
- `executeRepay`. The bootstrap, cashout, and debit adapters decode fixed-stride
423
- calldata input directly; the other three
424
- decode memory-backed pipeline state. All avoid an external self-call. Pass the
425
- step value into each adapter. Bootstrap is pipeline-local rather than 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
468
+ external self-call. The host's `execute` hook must authorize a command before
469
+ returning true. Returning `handled = false` delegates a local command to its
470
+ trusted normal external entrypoint. Pass the step value into each adapter.
471
+ Bootstrap is pipeline-local rather than an
426
472
  externally callable command and may consume value for native-asset balance;
427
473
  the other five reject nonzero value because those commands are non-funded.
428
474
 
@@ -460,8 +506,14 @@ the descriptor's lanes through the published block schemas.
460
506
 
461
507
  ## Ports
462
508
 
463
- Ports are the host-to-host surfaces, callable only by trusted peer hosts. The two
464
- 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:
465
517
 
466
518
  - `portPost` consumes `transaction { bytes32 from, bytes32 to, bytes32 asset,
467
519
  uint amount }` blocks, debiting `from` and crediting `to` per
@@ -470,14 +522,16 @@ central ones are batches all the way down:
470
522
  passes the authenticated peer, asset, and amount to a host hook. The hook
471
523
  validates asset support and applies the host's request and transfer policy.
472
524
  - `portRequestAllowance` consumes the same amount blocks and lets the
473
- authenticated peer request an asset allowance. The hook decides what
474
- 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.
475
527
  - `portPipePayable` consumes `context` blocks, each carrying an account, an
476
528
  initial state, and a run of steps — a complete pipeline delivered by another
477
529
  host, executed locally against the port call's shared value budget.
478
530
 
479
- This is also the cross-portal mechanism. `relayPayable`, `relayBalancePayable`,
480
- 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;
481
535
  a bridge adapter moves the **raw
482
536
  bytes**; the destination host parses them with the same cursor rules and runs
483
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.