@rootzero/contracts 1.27.0 → 1.29.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,43 @@ sections are immutable and must continue to describe the tagged release.
8
8
 
9
9
  ## Unreleased
10
10
 
11
+ ## 1.29.0
12
+
13
+ ### Breaking Changes
14
+
15
+ - `ExecuteHook.execute` and the optimized local command helpers now return a
16
+ leading `bool handled`. A local command returned as unhandled is invoked
17
+ through its trusted normal external command entrypoint; handled commands keep
18
+ the optimized internal path and are authorized by the hook implementation.
19
+ - Renamed `CashoutInternal`, `DebitAccountInternal`, `CreditAccountInternal`,
20
+ `SettleInternal`, and `RepayInternal` to `ExecuteCashout`,
21
+ `ExecuteDebitAccount`, `ExecuteCreditAccount`, `ExecuteSettle`, and
22
+ `ExecuteRepay`.
23
+
24
+ ## 1.28.0
25
+
26
+ ### Breaking Changes
27
+
28
+ - Expanded STEP's native `value` field from `uint128` to `uint`, increasing an
29
+ empty STEP block from 64 to 80 bytes. Plain native values now use `uint`
30
+ throughout runtime APIs; packed `resources` remain separate `uint` words
31
+ whose EVM value lane is extracted explicitly with `useResourceValue`.
32
+ - Replaced the `CommandCalls` abstraction and its allocating `encodeCommandCall`
33
+ and `callCommand` helpers with the free `rawCommandCall` function. The caller
34
+ now supplies the decoded selector and target after validating and authorizing
35
+ the command, then uses the single-scratch assembly call path.
36
+ - Added the required `ExecuteHook` to `Pipeline` and removed its `dispatch` hook.
37
+ Steps whose command IDs target the current host use `execute`; other commands
38
+ are checked through `TrustAccess` and called directly with `rawCommandCall`.
39
+
40
+ ### Changed
41
+
42
+ - Added the free `unpackCommand` utility for validating a command node and
43
+ extracting its selector and target in one operation. Pipeline steps now use
44
+ it to validate command IDs and select local execution by target address.
45
+ - Simplified `NodeAccess.ensureTrusted` to a single mapping check; an unset zero
46
+ node already resolves to false like every other untrusted node.
47
+
11
48
  ## 1.27.0
12
49
 
13
50
  ### Breaking Changes
package/Core.sol CHANGED
@@ -12,9 +12,9 @@ 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 { CommandCalls, FailedCall, NodeCalls, PortCalls, RawNodeCalls } from "./core/Calls.sol";
15
+ import { FailedCall, NodeCalls, PortCalls, RawNodeCalls, rawCommandCall } from "./core/Calls.sol";
16
16
  import { EndpointBase, InputEndpointBase } from "./core/Endpoint.sol";
17
- import { PipeHook, Pipeline } from "./core/Pipeline.sol";
17
+ import { ExecuteHook, PipeHook, Pipeline } from "./core/Pipeline.sol";
18
18
  import { Budget, Budgets } from "./execution/Budget.sol";
19
19
  import { CreditAccountHook, DebitAccountHook, PostHook, RepayHook, SettleHook, Settlement } from "./core/Settlement.sol";
20
20
  import { Portal } from "./core/Portal.sol";
package/Endpoints.sol CHANGED
@@ -6,7 +6,7 @@ pragma solidity ^0.8.33;
6
6
 
7
7
  // Shared endpoint hooks
8
8
  import {Flags} from "./codec/Descriptors.sol";
9
- import {PipeHook} from "./core/Pipeline.sol";
9
+ import {ExecuteHook, PipeHook} from "./core/Pipeline.sol";
10
10
  import {CreditAccountHook, DebitAccountHook, PostHook, RepayHook, SettleHook} from "./core/Settlement.sol";
11
11
 
12
12
  // Commands
@@ -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
package/README.md CHANGED
@@ -365,7 +365,7 @@ A single command is rarely the whole story. A pipeline is a run of `#step`
365
365
  blocks executed in order within one transaction:
366
366
 
367
367
  ```txt
368
- step { uint cmd, uint128 value, #bytes as input }
368
+ step { uint cmd, uint value, #bytes as input }
369
369
  ```
370
370
 
371
371
  Each step names a command, the native value it may spend, and its input.
@@ -379,22 +379,26 @@ debits each asset through the standard account hook, introduces matching
379
379
  native asset through the same hook. Its pipeline-local implementation uses assigned step value first when
380
380
  bootstrapping the native asset, debits any remainder from the account, and
381
381
  returns unused assigned value as credit. Bootstrap is registered with command
382
- metadata but is only executable through local pipeline dispatch. This is the core of
382
+ metadata but is only executable through local pipeline execution. This is the core of
383
383
  `Pipeline.pipe`:
384
384
 
385
385
  ```solidity
386
386
  while (cur.more()) {
387
- (uint cmd, uint128 value, bytes calldata input) = cur.unpackStep();
387
+ (uint cmd, uint value, bytes calldata input) = cur.unpackStep();
388
+ (bytes4 selector, address target) = unpackCommand(cmd);
388
389
  if (budget < value) revert InsufficientValue();
389
390
  unchecked { budget -= value; }
391
+ bool handled;
392
+ bytes memory output;
390
393
  uint credit;
391
- (state, credit) = dispatch(
392
- cmd,
393
- account,
394
- state,
395
- input,
396
- value
397
- );
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;
398
402
  budget += credit;
399
403
  }
400
404
  if (state.length != 0) revert UnexpectedState();
@@ -407,20 +411,25 @@ settles that final value once.
407
411
  A transfer, for instance, is a two-step pipeline: `debitAccount` turns an
408
412
  `#amount` input into `#balance` state, and `payout` consumes that state
409
413
  toward a recipient. Because a pipeline is just blocks, it is also the unit of
410
- command batching. A step's `uint128 value` is drawn directly from the shared
411
- native-value budget. Transport envelopes retain separate chain-specific
412
- `resources` fields for adapters that also need gas or runtime parameters.
414
+ command batching. A step's plain `uint value` is drawn directly from the shared
415
+ native-value budget. Transport envelopes retain separate opaque, packed
416
+ chain-specific `resources` fields for adapters that also need gas or runtime
417
+ parameters. A `resources` word is never itself native value; EVM adapters use
418
+ `useResourceValue` to extract its low 128-bit value lane before spending it.
413
419
 
414
420
  Hosts that implement a pipeline locally can inherit `Bootstrap`,
415
- `CashoutInternal`, `DebitAccountInternal`, `CreditAccountInternal`,
416
- `SettleInternal`, and
417
- `RepayInternal` to register canonical command metadata while routing
421
+ `ExecuteCashout`, `ExecuteDebitAccount`, `ExecuteCreditAccount`,
422
+ `ExecuteSettle`, and
423
+ `ExecuteRepay` to register canonical command metadata while executing
418
424
  their local command IDs through `executeBootstrap`, `executeCashout`,
419
425
  `executeDebitAccount`, `executeCreditAccount`, `executeSettle`, and
420
426
  `executeRepay`. The bootstrap, cashout, and debit adapters decode fixed-stride
421
427
  calldata input directly; the other three
422
- decode memory-backed pipeline state. All avoid an external self-call. Pass the
423
- step value into each adapter. Bootstrap is pipeline-local rather than an
428
+ decode memory-backed pipeline state. All return `handled = true` and avoid an
429
+ external self-call. The host's `execute` hook must authorize a command before
430
+ returning true. Returning `handled = false` delegates a local command to its
431
+ trusted normal external entrypoint. Pass the step value into each adapter.
432
+ Bootstrap is pipeline-local rather than an
424
433
  externally callable command and may consume value for native-asset balance;
425
434
  the other five reject nonzero value because those commands are non-funded.
426
435
 
@@ -509,11 +518,11 @@ names, access sets, balances — from logs alone, with no artifact files.
509
518
  Import from the package entry points rather than deep paths:
510
519
 
511
520
  - `@rootzero/contracts/Core.sol` — `Host`, access control, `Balances`,
512
- `Settlement`, `PipeHook`, `Pipeline`, `Portal`, validator
521
+ `Settlement`, `ExecuteHook`, `PipeHook`, `Pipeline`, `Portal`, validator
513
522
  - `@rootzero/contracts/Commands.sol` — `CommandBase`, `Execution`, `Flags`,
514
523
  codec helpers, and shared value types for authoring custom commands
515
524
  - `@rootzero/contracts/Endpoints.sol` — command, admin, port, guard, and query
516
- mixins, their hooks (including `PipeHook`), and `Flags`
525
+ mixins, their hooks (including `ExecuteHook` and `PipeHook`), and `Flags`
517
526
  - `@rootzero/contracts/Codec.sol` — `Blocks`, calldata `Cur`/`Cursors`, memory
518
527
  `Memory`, `Writers`, `Schemas`, `Descriptors`, `Flags`, `Keys`, and
519
528
  `Specs`
package/Utils.sol CHANGED
@@ -10,7 +10,7 @@ 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 } from "./utils/Nodes.sol";
13
+ import { Nodes, unpackCommand } from "./utils/Nodes.sol";
14
14
  import { Layout } from "./utils/Layout.sol";
15
15
  import { BadAmount, InsufficientValue, InvalidAccount, InvalidAsset, InvalidContract, InvalidId, InvalidPreimage, MissingCursor, NotDivisible, OutOfBounds, UnauthorizedAsset, UnconsumedData, UnexpectedPosition, ValueOverflow, ZeroAddress, ZeroAmount} from "./utils/Errors.sol";
16
16
  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";
package/codec/Blocks.sol CHANGED
@@ -761,17 +761,17 @@ library Blocks {
761
761
  /// @param cmd Command identifier.
762
762
  /// @param value Native value assigned to the step.
763
763
  /// @param input Command input.
764
- function writeStep(bytes memory dst, uint i, uint cmd, uint128 value, bytes memory input) internal pure {
765
- uint len = 48 + Sizes.Header + input.length;
764
+ function writeStep(bytes memory dst, uint i, uint cmd, uint value, bytes memory input) internal pure {
765
+ uint len = 64 + Sizes.Header + input.length;
766
766
  uint key = uint32(Keys.Step);
767
767
  uint byteskey = uint32(Keys.Bytes);
768
768
  assembly ("memory-safe") {
769
769
  let p := add(add(dst, 0x20), i)
770
770
  mstore(p, or(shl(224, key), shl(192, len)))
771
771
  mstore(add(p, 0x08), cmd)
772
- mstore(add(p, 0x28), shl(128, value))
772
+ mstore(add(p, 0x28), value)
773
773
 
774
- let q := add(p, 0x38)
774
+ let q := add(p, 0x48)
775
775
  let inputlen := mload(input)
776
776
  mstore(q, or(shl(224, byteskey), shl(192, inputlen)))
777
777
  mcopy(add(q, 0x08), add(input, 0x20), inputlen)
@@ -1031,17 +1031,17 @@ library Blocks {
1031
1031
  }
1032
1032
 
1033
1033
  /// @notice Encode a STEP block at `i`, copying its nested input from calldata.
1034
- function copyStep(bytes memory dst, uint i, uint cmd, uint128 value, bytes calldata input) internal pure {
1035
- uint len = max32(48 + Sizes.Header + input.length);
1034
+ function copyStep(bytes memory dst, uint i, uint cmd, uint value, bytes calldata input) internal pure {
1035
+ uint len = max32(64 + Sizes.Header + input.length);
1036
1036
  uint key = uint32(Keys.Step);
1037
1037
  uint byteskey = uint32(Keys.Bytes);
1038
1038
  assembly ("memory-safe") {
1039
1039
  let p := add(add(dst, 0x20), i)
1040
1040
  mstore(p, or(shl(224, key), shl(192, len)))
1041
1041
  mstore(add(p, 0x08), cmd)
1042
- mstore(add(p, 0x28), shl(128, value))
1042
+ mstore(add(p, 0x28), value)
1043
1043
 
1044
- let q := add(p, 0x38)
1044
+ let q := add(p, 0x48)
1045
1045
  let inputlen := input.length
1046
1046
  mstore(q, or(shl(224, byteskey), shl(192, inputlen)))
1047
1047
  calldatacopy(add(q, 0x08), input.offset, inputlen)
@@ -1836,14 +1836,14 @@ library Blocks {
1836
1836
  /// @return end Absolute position after the block.
1837
1837
  function unpackStep(
1838
1838
  uint abs
1839
- ) internal pure returns (uint cmd, uint128 value, bytes calldata input, uint end) {
1839
+ ) internal pure returns (uint cmd, uint value, bytes calldata input, uint end) {
1840
1840
  uint limit;
1841
1841
  (abs, limit) = expectKey(abs, Keys.Step);
1842
1842
  assembly ("memory-safe") {
1843
1843
  cmd := calldataload(abs)
1844
- value := shr(128, calldataload(add(abs, 0x20)))
1844
+ value := calldataload(add(abs, 0x20))
1845
1845
  }
1846
- (input, end) = unpackBytes(abs + 48);
1846
+ (input, end) = unpackBytes(abs + 64);
1847
1847
  if (end != limit) revert InvalidBlock();
1848
1848
  }
1849
1849
 
@@ -2203,14 +2203,14 @@ library Blocks {
2203
2203
  /// @param value Native value assigned to the step.
2204
2204
  /// @param input Raw nested input payload.
2205
2205
  /// @return encoded Encoded STEP block bytes.
2206
- function createStep(uint cmd, uint128 value, bytes memory input) internal pure returns (bytes memory encoded) {
2206
+ function createStep(uint cmd, uint value, bytes memory input) internal pure returns (bytes memory encoded) {
2207
2207
  uint len = max32(Sizes.Step + input.length);
2208
2208
  encoded = allocate(len);
2209
2209
  writeStep(encoded, 0, cmd, value, input);
2210
2210
  }
2211
2211
 
2212
2212
  /// @notice Encode a STEP block by copying its nested input from calldata.
2213
- function createStepCopy(uint cmd, uint128 value, bytes calldata input) internal pure returns (bytes memory encoded) {
2213
+ function createStepCopy(uint cmd, uint value, bytes calldata input) internal pure returns (bytes memory encoded) {
2214
2214
  uint len = max32(Sizes.Step + input.length);
2215
2215
  encoded = allocate(len);
2216
2216
  copyStep(encoded, 0, cmd, value, input);
@@ -740,7 +740,7 @@ library Decoders {
740
740
  /// @return input Decoded command input.
741
741
  function unpackStep(
742
742
  Cur memory cur
743
- ) internal pure returns (uint cmd, uint128 value, bytes calldata input) {
743
+ ) internal pure returns (uint cmd, uint value, bytes calldata input) {
744
744
  uint abs = cur.state.absolute();
745
745
  uint end;
746
746
  (cmd, value, input, end) = Blocks.unpackStep(abs);
package/codec/Keys.sol CHANGED
@@ -39,7 +39,7 @@ library Keys {
39
39
  bytes4 constant Account = bytes4(keccak256("#account"));
40
40
  /// @dev Transfer record passed through the pipeline - (bytes32 from, bytes32 to, bytes32 asset, uint amount)
41
41
  bytes4 constant Transaction = bytes4(keccak256("#transaction"));
42
- /// @dev Sub-command invocation - (uint cmd, uint128 value, #bytes as input)
42
+ /// @dev Sub-command invocation - (uint cmd, uint value, #bytes as input)
43
43
  bytes4 constant Step = bytes4(keccak256("#step"));
44
44
  /// @dev Portal relay input - (uint portal, uint resources, #bytes as input)
45
45
  bytes4 constant Relay = bytes4(keccak256("#relay"));
package/codec/Schema.sol CHANGED
@@ -29,10 +29,11 @@ pragma solidity ^0.8.33;
29
29
  // - `portal` fields identify destination portal hosts. By convention the value
30
30
  // is the portal implementation's host ID; core passes it through unchanged
31
31
  // and hooks may validate or resolve it for their transport
32
- // - `resources` fields are chain-specific resource words. A portal adapter
33
- // interprets them for the destination runtime. EVM resources use the low
34
- // 128 bits as native value.
35
- // - STEP encodes native `value` directly as uint128; it does not carry a
32
+ // - `resources` fields are opaque chain-specific packed words, not native
33
+ // values. A portal adapter interprets them for the destination runtime. EVM
34
+ // resources use the low 128 bits as native value, extracted explicitly with
35
+ // `useResourceValue` before spending.
36
+ // - STEP encodes native `value` directly as uint; it does not carry a
36
37
  // chain-specific resources word
37
38
  // - dotted field names and aliases, e.g. `dst.portal` or `#bytes as dst.payload`,
38
39
  // are offchain projection metadata only and do not change runtime encoding
@@ -116,7 +117,7 @@ library Schemas {
116
117
 
117
118
  // Composite payloads
118
119
 
119
- string constant Step = "uint cmd, uint128 value, #bytes as input";
120
+ string constant Step = "uint cmd, uint value, #bytes as input";
120
121
  string constant Call = "uint target, uint resources, #bytes as payload";
121
122
  string constant Relay = "uint portal, uint resources, #bytes as input";
122
123
  string constant Dispatch = "uint portal, uint resources, #bytes as payload";
package/codec/Specs.sol CHANGED
@@ -21,8 +21,8 @@ library Sizes {
21
21
  uint constant B128 = Header + 4 * Word;
22
22
  /// @dev 8 header + 160 payload = 168 bytes total.
23
23
  uint constant B160 = Header + 5 * Word;
24
- /// @dev Minimum STEP size: 8 header + 32 command + 16 value + 8 nested BYTES header.
25
- uint constant Step = 2 * Header + Word + 16;
24
+ /// @dev Minimum STEP size: 8 header + 32 command + 32 value + 8 nested BYTES header.
25
+ uint constant Step = 2 * Header + 2 * Word;
26
26
  /// @dev STATUS block: 8 header + 32 status code = 40 bytes
27
27
  uint constant Status = B32;
28
28
  /// @dev CASHOUT block: 8 header + 32 native-asset amount = 40 bytes
@@ -64,7 +64,6 @@ library Specs {
64
64
  uint private constant Exact128 = 128 * SizeFields;
65
65
  uint private constant UnboundedHint128 = uint(128) << 136;
66
66
  uint private constant UnboundedMin40Hint256 = (uint(40) << 192) | (uint(256) << 136);
67
- uint private constant UnboundedMin56Hint256 = (uint(56) << 192) | (uint(256) << 136);
68
67
  uint private constant UnboundedMin72Hint256 = (uint(72) << 192) | (uint(256) << 136);
69
68
  uint private constant UnboundedMin48Hint512 = (uint(48) << 192) | (uint(512) << 136);
70
69
  uint private constant UnboundedMin104Hint256 = (uint(104) << 192) | (uint(256) << 136);
@@ -85,7 +84,7 @@ library Specs {
85
84
  uint constant String = uint(bytes32(Keys.String)) | UnboundedHint128;
86
85
  uint constant Account = uint(bytes32(Keys.Account)) | Exact32;
87
86
  uint constant Transaction = uint(bytes32(Keys.Transaction)) | Exact128;
88
- uint constant Step = uint(bytes32(Keys.Step)) | UnboundedMin56Hint256;
87
+ uint constant Step = uint(bytes32(Keys.Step)) | UnboundedMin72Hint256;
89
88
  uint constant Relay = uint(bytes32(Keys.Relay)) | UnboundedMin72Hint256;
90
89
  uint constant Context = uint(bytes32(Keys.Context)) | UnboundedMin48Hint512;
91
90
  uint constant Recover = uint(bytes32(Keys.Recover)) | UnboundedMin104Hint256;
package/codec/Writers.sol CHANGED
@@ -414,7 +414,7 @@ library Writers {
414
414
  /// @param cmd Command identifier to encode.
415
415
  /// @param value Native value to encode.
416
416
  /// @param input Command input to encode.
417
- function appendStep(Writer memory writer, uint cmd, uint128 value, bytes memory input) internal pure {
417
+ function appendStep(Writer memory writer, uint cmd, uint value, bytes memory input) internal pure {
418
418
  uint size = Sizes.Step + input.length;
419
419
  uint i = reserve(writer, size);
420
420
  Blocks.writeStep(writer.dst, i, cmd, value, input);
@@ -561,7 +561,7 @@ library Writers {
561
561
  }
562
562
 
563
563
  /// @notice Append a STEP block by copying its nested input from calldata.
564
- function copyStep(Writer memory writer, uint cmd, uint128 value, bytes calldata input) internal pure {
564
+ function copyStep(Writer memory writer, uint cmd, uint value, bytes calldata input) internal pure {
565
565
  uint size = Sizes.Step + input.length;
566
566
  uint i = reserve(writer, size);
567
567
  Blocks.copyStep(writer.dst, i, cmd, value, input);
@@ -52,14 +52,15 @@ abstract contract Bootstrap is CommandBase, DebitAccountHook {
52
52
  /// @param state Empty pipeline state required by the command schema.
53
53
  /// @param input BOOTSTRAP block stream.
54
54
  /// @param value Native value available to fund native-asset balances.
55
+ /// @return handled Always true because this helper executed the command.
55
56
  /// @return output One BALANCE block per BOOTSTRAP input.
56
57
  /// @return credit Sourced budget contributions plus unused assigned value.
57
58
  function executeBootstrap(
58
59
  bytes32 account,
59
60
  bytes memory state,
60
61
  bytes calldata input,
61
- uint128 value
62
- ) internal returns (bytes memory output, uint credit) {
62
+ uint value
63
+ ) internal returns (bool handled, bytes memory output, uint credit) {
63
64
  if (state.length != 0) revert UnexpectedState();
64
65
  if (input.length % Sizes.Bootstrap != 0) revert Blocks.InvalidBlock();
65
66
 
@@ -77,5 +78,6 @@ abstract contract Bootstrap is CommandBase, DebitAccountHook {
77
78
  i += Sizes.Balance;
78
79
  }
79
80
  }
81
+ handled = true;
80
82
  }
81
83
  }
@@ -54,22 +54,23 @@ abstract contract Cashout is CommandBase, CashoutHook, Action {
54
54
  }
55
55
  }
56
56
 
57
- /// @title CashoutInternal
58
- /// @notice Extends cashout with optimized local pipeline dispatch.
59
- abstract contract CashoutInternal is Cashout {
57
+ /// @title ExecuteCashout
58
+ /// @notice Extends cashout with optimized local pipeline execution.
59
+ abstract contract ExecuteCashout is Cashout {
60
60
  /// @notice Execute cashout directly against a calldata CASHOUT stream.
61
61
  /// @param account Account whose native asset is withdrawn.
62
62
  /// @param state Empty pipeline state required by the command schema.
63
63
  /// @param input CASHOUT block stream.
64
64
  /// @param value Native value assigned to this command; must be zero.
65
+ /// @return handled Always true because this helper executed the command.
65
66
  /// @return output Empty output state.
66
67
  /// @return credit Zero native budget credit.
67
68
  function executeCashout(
68
69
  bytes32 account,
69
70
  bytes memory state,
70
71
  bytes calldata input,
71
- uint128 value
72
- ) internal returns (bytes memory, uint) {
72
+ uint value
73
+ ) internal returns (bool handled, bytes memory output, uint credit) {
73
74
  if (value != 0) revert ValueNotAllowed();
74
75
  if (state.length != 0) revert UnexpectedState();
75
76
  if (input.length % Sizes.Cashout != 0) revert Blocks.InvalidBlock();
@@ -81,6 +82,6 @@ abstract contract CashoutInternal is Cashout {
81
82
  abs += Sizes.Cashout;
82
83
  }
83
84
  }
84
- return ("", 0);
85
+ return (true, "", 0);
85
86
  }
86
87
  }
@@ -43,24 +43,25 @@ abstract contract CreditAccount is CommandBase, CreditAccountHook {
43
43
  }
44
44
  }
45
45
 
46
- /// @title CreditAccountInternal
47
- /// @notice Extends the advertised credit-account command with memory-state pipeline dispatch.
46
+ /// @title ExecuteCreditAccount
47
+ /// @notice Extends the advertised credit-account command with memory-state pipeline execution.
48
48
  /// @dev This adapter is not a separate command. It uses the command ID and account hook
49
49
  /// inherited from `CreditAccount` while accepting the state location used by `Pipeline`.
50
- abstract contract CreditAccountInternal is CreditAccount {
50
+ abstract contract ExecuteCreditAccount is CreditAccount {
51
51
  /// @notice Execute the inherited credit-account command from an internal pipeline.
52
52
  /// @param account Account credited by each balance.
53
53
  /// @param state BALANCE block stream held in pipeline memory.
54
54
  /// @param input Empty input required by the command schema.
55
55
  /// @param value Native value assigned to the command; must be zero.
56
+ /// @return handled Always true because this helper executed the command.
56
57
  /// @return output Empty output state.
57
58
  /// @return credit Zero native budget credit.
58
59
  function executeCreditAccount(
59
60
  bytes32 account,
60
61
  bytes memory state,
61
62
  bytes calldata input,
62
- uint128 value
63
- ) internal returns (bytes memory, uint) {
63
+ uint value
64
+ ) internal returns (bool handled, bytes memory output, uint credit) {
64
65
  if (value != 0) revert ValueNotAllowed();
65
66
  if (input.length != 0) revert UnexpectedInput();
66
67
  if (state.length == 0) revert Blocks.EmptyRun();
@@ -74,6 +75,6 @@ abstract contract CreditAccountInternal is CreditAccount {
74
75
  }
75
76
  }
76
77
 
77
- return ("", 0);
78
+ return (true, "", 0);
78
79
  }
79
80
  }
@@ -46,31 +46,32 @@ abstract contract DebitAccount is CommandBase, DebitAccountHook {
46
46
  }
47
47
  }
48
48
 
49
- /// @title DebitAccountInternal
50
- /// @notice Extends the advertised debit-account command with optimized pipeline dispatch.
49
+ /// @title ExecuteDebitAccount
50
+ /// @notice Extends the advertised debit-account command with optimized pipeline execution.
51
51
  /// @dev This adapter is not a separate command. It uses the command ID and account hook
52
52
  /// inherited from `DebitAccount` while decoding its fixed-stride input directly from calldata.
53
- abstract contract DebitAccountInternal is DebitAccount {
53
+ abstract contract ExecuteDebitAccount is DebitAccount {
54
54
  /// @notice Execute the inherited debit-account command from an internal pipeline.
55
55
  /// @param account Account whose funds are debited.
56
56
  /// @param state Empty pipeline state required by the command schema.
57
57
  /// @param input AMOUNT block stream.
58
58
  /// @param value Native value assigned to the command; must be zero.
59
+ /// @return handled Always true because this helper executed the command.
59
60
  /// @return output BALANCE block stream matching the debited amounts.
60
61
  /// @return credit Zero native budget credit.
61
62
  function executeDebitAccount(
62
63
  bytes32 account,
63
64
  bytes memory state,
64
65
  bytes calldata input,
65
- uint128 value
66
- ) internal returns (bytes memory, uint) {
66
+ uint value
67
+ ) internal returns (bool handled, bytes memory output, uint credit) {
67
68
  if (value != 0) revert ValueNotAllowed();
68
69
  if (state.length != 0) revert UnexpectedState();
69
70
  if (input.length == 0) revert Blocks.EmptyRun();
70
71
  if (input.length % Sizes.Amount != 0) revert Blocks.InvalidBlock();
71
72
 
72
73
  (uint abs, uint end) = Cursors.bounds(input);
73
- bytes memory output = new bytes(input.length);
74
+ output = new bytes(input.length);
74
75
  uint i;
75
76
 
76
77
  while (abs < end) {
@@ -83,6 +84,6 @@ abstract contract DebitAccountInternal is DebitAccount {
83
84
  }
84
85
  }
85
86
 
86
- return (output, 0);
87
+ return (true, output, 0);
87
88
  }
88
89
  }
@@ -9,7 +9,8 @@ using Executions for Execution;
9
9
  abstract contract RecoverPayableHook {
10
10
  /// @notice Override to recover a witness through `handler`.
11
11
  /// @param handler Port that should attempt recovery.
12
- /// @param resources Chain-specific resources assigned to the recovery attempt.
12
+ /// @param resources Opaque packed chain-specific resources, not plain native
13
+ /// value. EVM handlers extract the low 128-bit value lane with `useResourceValue`.
13
14
  /// @param key Recovery lookup key.
14
15
  /// @param witness Witness payload used to prove and replay recovery.
15
16
  /// @param funds Shared execution containing the source value budget.
@@ -13,8 +13,9 @@ abstract contract RelayPayableHook {
13
13
  /// the source state is consumed before destination success is known.
14
14
  /// @param portal Destination portal implementation's host ID. Implementations
15
15
  /// may validate or resolve it for their transport.
16
- /// @param resources Chain-specific destination resources. EVM adapters
17
- /// may interpret this as packed execution gas and destination value.
16
+ /// @param resources Opaque packed chain-specific destination resources, not
17
+ /// plain native value. EVM adapters extract the low 128-bit value lane with
18
+ /// `useResourceValue`; higher bits may encode execution gas or other data.
18
19
  /// @param account Destination command account.
19
20
  /// @param state Complete state forwarded into the destination context.
20
21
  /// @param input Input forwarded into the destination context.
@@ -146,24 +146,25 @@ abstract contract RepayPositionPayable is CommandBase, RepayPayableHook, Action
146
146
  }
147
147
  }
148
148
 
149
- /// @title RepayInternal
150
- /// @notice Extends the advertised repay command with memory-state pipeline dispatch.
149
+ /// @title ExecuteRepay
150
+ /// @notice Extends the advertised repay command with memory-state pipeline execution.
151
151
  /// @dev This adapter is not a separate command. It uses the command ID and repayment hook
152
152
  /// inherited from `Repay` while accepting the state location used by `Pipeline`.
153
- abstract contract RepayInternal is Repay {
153
+ abstract contract ExecuteRepay is Repay {
154
154
  /// @notice Execute the inherited repay command from an internal pipeline.
155
155
  /// @param account Account whose liabilities are repaid.
156
156
  /// @param state DEBT block stream held in pipeline memory.
157
157
  /// @param input Empty input required by the command schema.
158
158
  /// @param value Native value assigned to the command; must be zero.
159
+ /// @return handled Always true because this helper executed the command.
159
160
  /// @return output Empty output state.
160
161
  /// @return credit Zero native budget credit.
161
162
  function executeRepay(
162
163
  bytes32 account,
163
164
  bytes memory state,
164
165
  bytes calldata input,
165
- uint128 value
166
- ) internal returns (bytes memory, uint) {
166
+ uint value
167
+ ) internal returns (bool handled, bytes memory output, uint credit) {
167
168
  if (value != 0) revert ValueNotAllowed();
168
169
  if (input.length != 0) revert UnexpectedInput();
169
170
  if (state.length == 0) revert Blocks.EmptyRun();
@@ -177,6 +178,6 @@ abstract contract RepayInternal is Repay {
177
178
  }
178
179
  }
179
180
 
180
- return ("", 0);
181
+ return (true, "", 0);
181
182
  }
182
183
  }
@@ -93,24 +93,25 @@ abstract contract SettlePayable is CommandBase, SettlePayableHook, Action {
93
93
  }
94
94
  }
95
95
 
96
- /// @title SettleInternal
97
- /// @notice Extends the advertised settle command with memory-state pipeline dispatch.
96
+ /// @title ExecuteSettle
97
+ /// @notice Extends the advertised settle command with memory-state pipeline execution.
98
98
  /// @dev This adapter is not a separate command. It uses the command ID and settlement hook
99
99
  /// inherited from `Settle` while accepting the state location used by `Pipeline`.
100
- abstract contract SettleInternal is Settle {
100
+ abstract contract ExecuteSettle is Settle {
101
101
  /// @notice Execute the inherited settle command from an internal pipeline.
102
102
  /// @param account Account for which each position is settled.
103
103
  /// @param state POSITION block stream held in pipeline memory.
104
104
  /// @param input Empty input required by the command schema.
105
105
  /// @param value Native value assigned to the command; must be zero.
106
+ /// @return handled Always true because this helper executed the command.
106
107
  /// @return output Empty output state.
107
108
  /// @return credit Zero native budget credit.
108
109
  function executeSettle(
109
110
  bytes32 account,
110
111
  bytes memory state,
111
112
  bytes calldata input,
112
- uint128 value
113
- ) internal returns (bytes memory, uint) {
113
+ uint value
114
+ ) internal returns (bool handled, bytes memory output, uint credit) {
114
115
  if (value != 0) revert ValueNotAllowed();
115
116
  if (input.length != 0) revert UnexpectedInput();
116
117
  if (state.length == 0) revert Blocks.EmptyRun();
@@ -124,6 +125,6 @@ abstract contract SettleInternal is Settle {
124
125
  }
125
126
  }
126
127
 
127
- return ("", 0);
128
+ return (true, "", 0);
128
129
  }
129
130
  }
@@ -8,7 +8,9 @@ using Executions for Execution;
8
8
 
9
9
  /// @title ExecutePayable
10
10
  /// @notice Admin command that forwards raw calldata to one or more target nodes.
11
- /// Each CALL block specifies a target node ID, packed resources, and raw calldata payload.
11
+ /// Each CALL block specifies a target node ID, opaque packed resources, and raw
12
+ /// calldata payload. Packed resources are converted to plain native value only
13
+ /// through `useResourceValue`.
12
14
  /// Only callable by the admin account.
13
15
  /// Unspent top-level `msg.value` is returned as native budget credit.
14
16
  abstract contract ExecutePayable is RawNodeCalls, AdminBase {
package/core/Access.sol CHANGED
@@ -106,9 +106,7 @@ abstract contract NodeAccess is AdminAccess, TrustAccess, NodeEvent {
106
106
  /// @param node Node ID to validate.
107
107
  /// @return The same `node` value if trusted.
108
108
  function ensureTrusted(uint node) internal view override returns (uint) {
109
- if (node == 0 || !nodes[node]) {
110
- revert AccessDenied();
111
- }
109
+ if (!nodes[node]) revert AccessDenied();
112
110
  return node;
113
111
  }
114
112
 
package/core/Calls.sol CHANGED
@@ -3,9 +3,6 @@ pragma solidity ^0.8.33;
3
3
 
4
4
  import {TrustAccess} from "./Access.sol";
5
5
  import {Nodes} from "../utils/Nodes.sol";
6
- import {Keys} from "../codec/Keys.sol";
7
- import {Sizes} from "../codec/Specs.sol";
8
- import {max32} from "../utils/Utils.sol";
9
6
 
10
7
  /// @dev Emitted when a trusted inter-node call fails.
11
8
  /// @param addr Contract address that was called.
@@ -13,6 +10,81 @@ import {max32} from "../utils/Utils.sol";
13
10
  /// @param err Revert data returned by the failed call.
14
11
  error FailedCall(address addr, bytes4 selector, bytes err);
15
12
 
13
+ /// @notice Execute a raw `command(bytes)` call and decode its state and credit results.
14
+ /// @dev The caller must validate and authorize the command before entering this helper.
15
+ /// Uses one scratch region for both call data and return data. Successful return data
16
+ /// must use the exact ABI layout of `(bytes, uint)`.
17
+ function rawCommandCall(
18
+ bytes4 selector,
19
+ address target,
20
+ uint value,
21
+ bytes32 account,
22
+ bytes memory state,
23
+ bytes calldata input
24
+ ) returns (bytes memory output, uint credit) {
25
+ assembly ("memory-safe") {
26
+ let scratch := mload(0x40)
27
+ let statelen := mload(state)
28
+ let ctxlen := add(56, add(statelen, input.length))
29
+
30
+ // ABI envelope for command(bytes).
31
+ mstore(scratch, selector)
32
+ mstore(add(scratch, 0x04), 0x20)
33
+ mstore(add(scratch, 0x24), ctxlen)
34
+
35
+ // CONTEXT(account, BYTES(state), BYTES(input)).
36
+ let ctx := add(scratch, 0x44)
37
+ // 0xc5769e23 = bytes4(keccak256("#context"))
38
+ mstore(ctx, or(shl(224, 0xc5769e23), shl(192, sub(ctxlen, 8))))
39
+ // `ctxlen` is dead after the header and can carry the complete call length.
40
+ ctxlen := add(68, and(add(ctxlen, 0x1f), not(0x1f)))
41
+ mstore(add(ctx, 0x08), account)
42
+ let stateblk := add(ctx, 0x28)
43
+ // 0x6911b332 = bytes4(keccak256("#bytes"))
44
+ mstore(stateblk, or(shl(224, 0x6911b332), shl(192, statelen)))
45
+ mcopy(add(stateblk, 0x08), add(state, 0x20), statelen)
46
+ let inputblk := add(add(stateblk, 0x08), statelen)
47
+ let inputlen := input.length
48
+ mstore(inputblk, or(shl(224, 0x6911b332), shl(192, inputlen)))
49
+ calldatacopy(add(inputblk, 0x08), input.offset, inputlen)
50
+
51
+ // ABI word alignment also covers the input header's full-word write. Advance
52
+ // the free memory pointer once, after the return-data size is also known.
53
+ let inputend := and(add(add(scratch, ctxlen), 0x1f), not(0x1f))
54
+ let success := call(gas(), target, value, scratch, ctxlen, 0, 0)
55
+ let retlen := returndatasize()
56
+ if iszero(success) {
57
+ // FailedCall(target, selector, returndata)
58
+ // 0x20577b07 = FailedCall(address,bytes4,bytes)
59
+ mstore(scratch, shl(224, 0x20577b07))
60
+ mstore(add(scratch, 0x04), target)
61
+ mstore(add(scratch, 0x24), selector)
62
+ mstore(add(scratch, 0x44), 0x60)
63
+ mstore(add(scratch, 0x64), retlen)
64
+ mstore(add(add(scratch, 0x84), retlen), 0)
65
+ returndatacopy(add(scratch, 0x84), 0, retlen)
66
+ revert(scratch, add(0x84, and(add(retlen, 0x1f), not(0x1f))))
67
+ }
68
+
69
+ if lt(retlen, 0x60) { revert(0, 0) }
70
+ returndatacopy(scratch, 0, retlen)
71
+
72
+ // Strictly validate the exact ABI layout for (bytes, uint). The returned
73
+ // byte array can then point directly into the copied returndata.
74
+ if iszero(eq(mload(scratch), 0x40)) { revert(0, 0) }
75
+ let len1 := mload(add(scratch, 0x40))
76
+ if gt(len1, sub(retlen, 0x60)) { revert(0, 0) }
77
+ let pad1 := and(add(len1, 0x1f), not(0x1f))
78
+ if iszero(eq(retlen, add(0x60, pad1))) { revert(0, 0) }
79
+ output := add(scratch, 0x40)
80
+ credit := mload(add(scratch, 0x20))
81
+
82
+ let retend := and(add(add(scratch, retlen), 0x1f), not(0x1f))
83
+ if gt(retend, inputend) { inputend := retend }
84
+ mstore(0x40, inputend)
85
+ }
86
+ }
87
+
16
88
  /// @title RawNodeCalls
17
89
  /// @notice Low-level inter-node call helpers without target authorization.
18
90
  abstract contract RawNodeCalls {
@@ -21,7 +93,7 @@ abstract contract RawNodeCalls {
21
93
  /// @param value Native value to forward in wei.
22
94
  /// @param data Encoded calldata to send.
23
95
  /// @return success True if the low-level call succeeded.
24
- function tryRawCall(uint node, uint128 value, bytes memory data) internal returns (bool success) {
96
+ function tryRawCall(uint node, uint value, bytes memory data) internal returns (bool success) {
25
97
  address addr = Nodes.addr(node);
26
98
  (success, ) = payable(addr).call{value: value}(data);
27
99
  }
@@ -31,7 +103,7 @@ abstract contract RawNodeCalls {
31
103
  /// @param value Native value to forward in wei.
32
104
  /// @param data Encoded calldata to send.
33
105
  /// @return out Return data from the successful call.
34
- function rawCall(uint node, uint128 value, bytes memory data) internal returns (bytes memory out) {
106
+ function rawCall(uint node, uint value, bytes memory data) internal returns (bytes memory out) {
35
107
  bool success;
36
108
  address addr = Nodes.addr(node);
37
109
  (success, out) = payable(addr).call{value: value}(data);
@@ -59,7 +131,7 @@ abstract contract NodeCalls is RawNodeCalls, TrustAccess {
59
131
  /// @param value Native value to forward in wei.
60
132
  /// @param data Encoded calldata to send.
61
133
  /// @return success True if the low-level call succeeded.
62
- function tryTrustedCall(uint node, uint128 value, bytes memory data) internal returns (bool success) {
134
+ function tryTrustedCall(uint node, uint value, bytes memory data) internal returns (bool success) {
63
135
  return tryRawCall(ensureTrusted(node), value, data);
64
136
  }
65
137
 
@@ -68,7 +140,7 @@ abstract contract NodeCalls is RawNodeCalls, TrustAccess {
68
140
  /// @param value Native value to forward in wei.
69
141
  /// @param data Encoded calldata to send.
70
142
  /// @return out Return data from the successful call.
71
- function trustedCall(uint node, uint128 value, bytes memory data) internal returns (bytes memory out) {
143
+ function trustedCall(uint node, uint value, bytes memory data) internal returns (bytes memory out) {
72
144
  return rawCall(ensureTrusted(node), value, data);
73
145
  }
74
146
 
@@ -81,74 +153,6 @@ abstract contract NodeCalls is RawNodeCalls, TrustAccess {
81
153
  }
82
154
  }
83
155
 
84
- /// @title CommandCalls
85
- /// @notice Trusted command-call helpers for contracts that route command nodes.
86
- abstract contract CommandCalls is NodeCalls {
87
- /// @dev Build `command(bytes)` calldata and its nested CONTEXT block in one allocation.
88
- /// Threaded state is copied from memory and step input directly from calldata.
89
- function encodeCommandCall(
90
- bytes4 selector,
91
- bytes32 account,
92
- bytes memory state,
93
- bytes calldata input
94
- ) internal pure returns (bytes memory data) {
95
- uint contextLen = max32(Sizes.B32 + 2 * Sizes.Header + state.length + input.length);
96
- uint paddedContextLen = (contextLen + 31) & ~uint(31);
97
- uint dataLen = 4 + 64 + paddedContextLen;
98
-
99
- // Reserve one scratch word because the final eight-byte block header is
100
- // written with mstore. Exclude that word from the returned calldata.
101
- data = new bytes(dataLen + 32);
102
-
103
- uint contextKey = uint32(Keys.Context);
104
- uint bytesKey = uint32(Keys.Bytes);
105
- assembly ("memory-safe") {
106
- mstore(data, dataLen)
107
- let out := add(data, 0x20)
108
-
109
- // ABI envelope for command(bytes).
110
- mstore(out, selector)
111
- mstore(add(out, 0x04), 0x20)
112
- mstore(add(out, 0x24), contextLen)
113
-
114
- // CONTEXT(account, BYTES(state), BYTES(input)).
115
- let context := add(out, 0x44)
116
- mstore(context, or(shl(224, contextKey), shl(192, sub(contextLen, 8))))
117
- mstore(add(context, 0x08), account)
118
-
119
- let stateBlock := add(context, 0x28)
120
- let stateLen := mload(state)
121
- mstore(stateBlock, or(shl(224, bytesKey), shl(192, stateLen)))
122
- mcopy(add(stateBlock, 0x08), add(state, 0x20), stateLen)
123
-
124
- let inputBlock := add(add(stateBlock, 0x08), stateLen)
125
- let inputLen := input.length
126
- mstore(inputBlock, or(shl(224, bytesKey), shl(192, inputLen)))
127
- calldatacopy(add(inputBlock, 0x08), input.offset, inputLen)
128
- }
129
- }
130
-
131
- /// @notice Encode and call a trusted command node.
132
- /// @param command Command node ID embedding the target selector.
133
- /// @param value Native value to forward in wei.
134
- /// @param account Command account identifier.
135
- /// @param state Current command state block stream.
136
- /// @param input Command input block stream.
137
- /// @return nextState Decoded command output state block stream.
138
- /// @return credit Trusted native value to add to the caller's execution budget.
139
- function callCommand(
140
- uint command,
141
- uint128 value,
142
- bytes32 account,
143
- bytes memory state,
144
- bytes calldata input
145
- ) internal returns (bytes memory nextState, uint credit) {
146
- bytes4 selector = Nodes.commandSelector(command);
147
- bytes memory data = encodeCommandCall(selector, account, state, input);
148
- return abi.decode(trustedCall(command, value, data), (bytes, uint));
149
- }
150
- }
151
-
152
156
  /// @title PortCalls
153
157
  /// @notice Trusted port-call helpers for contracts that route port nodes.
154
158
  abstract contract PortCalls is NodeCalls {
@@ -157,7 +161,7 @@ abstract contract PortCalls is NodeCalls {
157
161
  /// @param value Native value to forward in wei.
158
162
  /// @param input Port input block stream.
159
163
  /// @return success True if the low-level port call succeeded.
160
- function tryCallPort(uint port, uint128 value, bytes memory input) internal returns (bool success) {
164
+ function tryCallPort(uint port, uint value, bytes memory input) internal returns (bool success) {
161
165
  bytes4 selector = Nodes.portSelector(port);
162
166
  bytes memory data = abi.encodeWithSelector(selector, input);
163
167
  return tryTrustedCall(port, value, data);
@@ -168,7 +172,7 @@ abstract contract PortCalls is NodeCalls {
168
172
  /// @param value Native value to forward in wei.
169
173
  /// @param input Port input block stream.
170
174
  /// @return success True if the low-level port call succeeded.
171
- function tryCallPortCopy(uint port, uint128 value, bytes calldata input) internal returns (bool success) {
175
+ function tryCallPortCopy(uint port, uint value, bytes calldata input) internal returns (bool success) {
172
176
  bytes4 selector = Nodes.portSelector(port);
173
177
  bytes memory data = abi.encodeWithSelector(selector, input);
174
178
  return tryTrustedCall(port, value, data);
@@ -179,7 +183,7 @@ abstract contract PortCalls is NodeCalls {
179
183
  /// @param value Native value to forward in wei.
180
184
  /// @param input Port input block stream.
181
185
  /// @return Decoded port output block stream.
182
- function callPort(uint port, uint128 value, bytes memory input) internal returns (bytes memory) {
186
+ function callPort(uint port, uint value, bytes memory input) internal returns (bytes memory) {
183
187
  bytes4 selector = Nodes.portSelector(port);
184
188
  bytes memory data = abi.encodeWithSelector(selector, input);
185
189
  return abi.decode(trustedCall(port, value, data), (bytes));
@@ -190,7 +194,7 @@ abstract contract PortCalls is NodeCalls {
190
194
  /// @param value Native value to forward in wei.
191
195
  /// @param input Port input block stream.
192
196
  /// @return Decoded port output block stream.
193
- function callPortCopy(uint port, uint128 value, bytes calldata input) internal returns (bytes memory) {
197
+ function callPortCopy(uint port, uint value, bytes calldata input) internal returns (bytes memory) {
194
198
  bytes4 selector = Nodes.portSelector(port);
195
199
  bytes memory data = abi.encodeWithSelector(selector, input);
196
200
  return abi.decode(trustedCall(port, value, data), (bytes));
package/core/Pipeline.sol CHANGED
@@ -2,8 +2,11 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import {Blocks} from "../codec/Blocks.sol";
5
+ import {TrustAccess} from "./Access.sol";
6
+ import {rawCommandCall} from "./Calls.sol";
5
7
  import {Cursors} from "../utils/Cursors.sol";
6
8
  import {InsufficientValue, OutOfBounds, UnexpectedState} from "../utils/Errors.sol";
9
+ import {unpackCommand} from "../utils/Nodes.sol";
7
10
 
8
11
  /// @notice Hook implemented by hosts that execute encoded step streams.
9
12
  abstract contract PipeHook {
@@ -16,27 +19,40 @@ abstract contract PipeHook {
16
19
  ) internal virtual returns (uint remaining);
17
20
  }
18
21
 
22
+ /// @notice Hook implemented by pipeline hosts that execute host-local commands.
23
+ abstract contract ExecuteHook {
24
+ /// @notice Try to execute one command whose node ID targets the current host.
25
+ /// @dev Implementations returning `handled = true` are responsible for
26
+ /// authorizing the command. Return false without side effects to delegate to
27
+ /// the trusted normal external entrypoint. Implementations may revert instead.
28
+ function execute(
29
+ uint cmd,
30
+ bytes32 account,
31
+ bytes memory state,
32
+ bytes calldata input,
33
+ uint value
34
+ ) internal virtual returns (bool handled, bytes memory output, uint credit);
35
+ }
36
+
19
37
  /// @title Pipeline
20
38
  /// @notice Core pipeline functionality shared by higher-level surfaces.
21
- abstract contract Pipeline is PipeHook {
22
- /// @notice Override to dispatch one piped step.
23
- /// Called once per STEP block. The returned state becomes the state passed to
24
- /// the next step, and the final returned state must be empty. Returned
25
- /// credit is added to the shared native-value budget before the next step runs.
26
- /// @param cmd Command node ID to invoke or handle.
27
- /// @param account Account identifier for the piped context.
28
- /// @param state Current threaded state block stream.
29
- /// @param input Step input block stream.
30
- /// @param value Native EVM value assigned to this step.
31
- /// @return output Updated state block stream for the next step.
32
- /// @return credit Trusted native value to add to the pipeline budget.
33
- function dispatch(
39
+ abstract contract Pipeline is TrustAccess, PipeHook, ExecuteHook {
40
+ function run(
34
41
  uint cmd,
35
42
  bytes32 account,
36
43
  bytes memory state,
37
44
  bytes calldata input,
38
- uint128 value
39
- ) internal virtual returns (bytes memory output, uint credit);
45
+ uint value
46
+ ) private returns (bytes memory output, uint credit) {
47
+ (bytes4 selector, address target) = unpackCommand(cmd);
48
+ if (target == address(this)) {
49
+ bool handled;
50
+ (handled, output, credit) = execute(cmd, account, state, input, value);
51
+ if (handled) return (output, credit);
52
+ }
53
+ ensureTrusted(cmd);
54
+ return rawCommandCall(selector, target, value, account, state, input);
55
+ }
40
56
 
41
57
  /// @notice Execute a STEP block stream through the pipeline.
42
58
  /// @dev Reverts with `UnexpectedState` if the final threaded state is non-empty.
@@ -55,18 +71,15 @@ abstract contract Pipeline is PipeHook {
55
71
  (uint abs, uint end) = Cursors.bounds(steps);
56
72
 
57
73
  while (abs < end) {
58
- uint cmd;
59
- uint128 value;
60
- bytes calldata input;
61
- (cmd, value, input, abs) = Blocks.unpackStep(abs);
62
- if (abs > end) revert OutOfBounds();
74
+ (uint cmd, uint value, bytes calldata input, uint next) = Blocks.unpackStep(abs);
75
+ if (next > end) revert OutOfBounds();
63
76
  if (value > budget) revert InsufficientValue();
64
77
  unchecked {
65
78
  budget -= value;
66
79
  }
67
- uint credit;
68
- (state, credit) = dispatch(cmd, account, state, input, value);
69
- budget += credit;
80
+ (state, value) = run(cmd, account, state, input, value);
81
+ budget += value;
82
+ abs = next;
70
83
  }
71
84
 
72
85
  if (state.length != 0) revert UnexpectedState();
package/core/Portal.sol CHANGED
@@ -20,7 +20,7 @@ abstract contract Portal is PortCalls, NodeAccess, UnresolvedEvent, ResolvedEven
20
20
  /// @param message Encoded port input to forward.
21
21
  /// @param value Native EVM value assigned to the forwarding attempt.
22
22
  /// @return miss Message digest recorded for recovery when forwarding fails; zero on success.
23
- function forward(uint port, bytes32 key, bytes calldata message, uint128 value) internal returns (bytes32 miss) {
23
+ function forward(uint port, bytes32 key, bytes calldata message, uint value) internal returns (bytes32 miss) {
24
24
  if (tryCallPortCopy(port, value, message)) return bytes32(0);
25
25
 
26
26
  miss = keccak256(message);
@@ -33,7 +33,7 @@ abstract contract Portal is PortCalls, NodeAccess, UnresolvedEvent, ResolvedEven
33
33
  /// @param key Recovery lookup key.
34
34
  /// @param witness Witness payload used to prove and replay recovery.
35
35
  /// @param value Native EVM value assigned to the resolution attempt.
36
- function resolve(uint port, bytes32 key, bytes calldata witness, uint128 value) internal virtual {
36
+ function resolve(uint port, bytes32 key, bytes calldata witness, uint value) internal virtual {
37
37
  if (unresolved[key] != keccak256(witness)) revert BadWitness();
38
38
 
39
39
  delete unresolved[key];
@@ -38,15 +38,19 @@ library Budgets {
38
38
  }
39
39
 
40
40
  /// @notice Deduct the EVM value lane of `resources` from `budget`.
41
- /// @dev EVM resources use the low 128 bits as native value/endowment.
41
+ /// @dev `resources` is not a native value. This helper explicitly extracts
42
+ /// its low 128-bit EVM value lane and widens that lane to a plain `uint`.
42
43
  /// @param budget Mutable budget to debit.
43
44
  /// @param resources Packed resources whose low 128 bits contain native value.
44
45
  /// @return value Native value to forward in wei.
45
- function useResourceValue(Budget memory budget, uint resources) internal pure returns (uint128) {
46
- return uint128(useValue(budget, uint128(resources)));
46
+ function useResourceValue(Budget memory budget, uint resources) internal pure returns (uint value) {
47
+ value = uint128(resources);
48
+ useValue(budget, value);
47
49
  }
48
50
 
49
51
  /// @notice Deduct the EVM value lane of `resources` from a scalar budget.
52
+ /// @dev `resources` is not a native value. This helper explicitly extracts
53
+ /// its low 128-bit EVM value lane and widens that lane to a plain `uint`.
50
54
  /// @param budget Remaining native value in wei.
51
55
  /// @param resources Packed resources whose low 128 bits contain native value.
52
56
  /// @return remaining Native value remaining after the deduction.
@@ -54,7 +58,7 @@ library Budgets {
54
58
  function useResourceValue(
55
59
  uint budget,
56
60
  uint resources
57
- ) internal pure returns (uint remaining, uint128 value) {
61
+ ) internal pure returns (uint remaining, uint value) {
58
62
  value = uint128(resources);
59
63
  remaining = useValue(budget, value);
60
64
  }
@@ -618,7 +618,7 @@ library Executions {
618
618
  /// @return cmd Decoded command identifier.
619
619
  /// @return value Decoded native value.
620
620
  /// @return input Decoded nested input.
621
- function unpackStep(Execution memory exec) internal pure returns (uint cmd, uint128 value, bytes calldata input) {
621
+ function unpackStep(Execution memory exec) internal pure returns (uint cmd, uint value, bytes calldata input) {
622
622
  uint cur = exec.decoders;
623
623
  uint end;
624
624
  (cmd, value, input, end) = Blocks.unpackStep(cur.absolute());
@@ -1029,7 +1029,7 @@ library Executions {
1029
1029
  /// @param cmd Command identifier to encode.
1030
1030
  /// @param value Native value to encode.
1031
1031
  /// @param input Command input to encode.
1032
- function outputStep(Execution memory exec, uint cmd, uint128 value, bytes memory input) internal pure {
1032
+ function outputStep(Execution memory exec, uint cmd, uint value, bytes memory input) internal pure {
1033
1033
  uint size = Sizes.Step + input.length;
1034
1034
  uint i = reserve(exec, size);
1035
1035
  Blocks.writeStep(exec.output, i, cmd, value, input);
@@ -1164,7 +1164,7 @@ library Executions {
1164
1164
  }
1165
1165
 
1166
1166
  /// @notice Append a STEP block to execution output by copying its nested input from calldata.
1167
- function outputCopyStep(Execution memory exec, uint cmd, uint128 value, bytes calldata input) internal pure {
1167
+ function outputCopyStep(Execution memory exec, uint cmd, uint value, bytes calldata input) internal pure {
1168
1168
  uint size = Sizes.Step + input.length;
1169
1169
  uint i = reserve(exec, size);
1170
1170
  Blocks.copyStep(exec.output, i, cmd, value, input);
@@ -1247,12 +1247,14 @@ library Executions {
1247
1247
  }
1248
1248
 
1249
1249
  /// @notice Deduct the EVM value lane of `resources` from the execution budget.
1250
- /// @dev EVM resources use the low 128 bits as native value/endowment.
1250
+ /// @dev `resources` is not a native value. This helper explicitly extracts
1251
+ /// its low 128-bit EVM value lane and widens that lane to a plain `uint`.
1251
1252
  /// @param exec Mutable execution whose budget is charged.
1252
1253
  /// @param resources Packed resources whose value lane should be spent.
1253
1254
  /// @return value Native value to forward in wei.
1254
- function useResourceValue(Execution memory exec, uint resources) internal pure returns (uint128) {
1255
- return uint128(useValue(exec, uint128(resources)));
1255
+ function useResourceValue(Execution memory exec, uint resources) internal pure returns (uint value) {
1256
+ value = uint128(resources);
1257
+ useValue(exec, value);
1256
1258
  }
1257
1259
 
1258
1260
  // -------------------------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rootzero/contracts",
3
- "version": "1.27.0",
3
+ "version": "1.29.0",
4
4
  "description": "Solidity contracts and protocol building blocks for rootzero hosts and commands.",
5
5
  "private": false,
6
6
  "license": "GPL-3.0-only",
@@ -13,8 +13,9 @@ abstract contract DispatchPayableHook {
13
13
  /// @notice Override to dispatch an encoded payload to `portal`.
14
14
  /// @param portal Destination portal implementation's host ID. Implementations
15
15
  /// may validate or resolve it for their transport.
16
- /// @param resources Chain-specific destination resources. EVM adapters
17
- /// may interpret this as packed execution gas and destination value.
16
+ /// @param resources Opaque packed chain-specific destination resources, not
17
+ /// plain native value. EVM adapters extract the low 128-bit value lane with
18
+ /// `useResourceValue`; higher bits may encode execution gas or other data.
18
19
  /// @param payload Encoded payload ready for the transport layer.
19
20
  /// @param funds Execution used for source value available for transport fees
20
21
  /// and destination resource funding.
package/utils/Nodes.sol CHANGED
@@ -6,6 +6,21 @@ import {InvalidId} from "./Errors.sol";
6
6
  import {Ids} from "./Ids.sol";
7
7
  import {ensureAddr, isFamily, matchesBase, toLocalBase} from "./Utils.sol";
8
8
 
9
+ /// @notice Validate and unpack a command node into its ABI selector and target address.
10
+ /// @dev Validates only the command type prefix; chain locality and authorization are caller concerns.
11
+ /// @param cmd Command node ID to unpack.
12
+ /// @return selector ABI selector stored in bits [191:160].
13
+ /// @return target Contract address stored in bits [159:0].
14
+ function unpackCommand(uint cmd) pure returns (bytes4 selector, address target) {
15
+ uint32 command = (uint32(Layout.Evm) << 16) | (uint32(Layout.Node) << 8) | uint32(Layout.Command);
16
+ if (uint32(cmd >> 224) != command) revert InvalidId();
17
+
18
+ assembly ("memory-safe") {
19
+ selector := shl(224, and(shr(160, cmd), 0xffffffff))
20
+ target := and(cmd, 0xffffffffffffffffffffffffffffffffffffffff)
21
+ }
22
+ }
23
+
9
24
  /// @title Nodes
10
25
  /// @notice Encoding and decoding helpers for 256-bit node identifiers.
11
26
  ///