@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.
@@ -71,29 +71,29 @@ library Executions {
71
71
  return exec.decoders.absolute();
72
72
  }
73
73
 
74
- /// @dev Return the complete bounded calldata source for a decoder lane.
75
- /// @dev Does not consume or depend on the lane's current position. A lane
76
- /// absent because its descriptor is EMPTY returns an empty calldata slice.
74
+ /// @dev Return the unread bounded calldata source for a decoder lane.
75
+ /// A lane absent because its descriptor is EMPTY returns an empty calldata slice.
77
76
  /// @param exec Execution whose input or state source is requested.
78
- /// @return data Complete calldata region represented by the lane.
77
+ /// @return data Unread calldata region from the lane's current position.
79
78
  function raw(Execution memory exec, uint8 lane) private pure returns (bytes calldata data) {
80
79
  if (!exec.decoders.contains(lane)) return msg.data[0:0];
81
80
 
82
81
  uint cur = exec.decoders.select(lane);
83
82
  if (uint8(cur >> 96) == 0) return msg.data[0:0];
84
- (uint abs, uint end) = cur.bounds();
85
- if (end > msg.data.length) revert Blocks.MalformedBlocks();
86
- return msg.data[abs:end];
83
+ (uint i, uint offset, uint len) = cur.decode();
84
+ if (len > msg.data.length || offset > msg.data.length - len) {
85
+ revert Blocks.MalformedBlocks();
86
+ }
87
+ return msg.data[offset + i:offset + len];
87
88
  }
88
89
 
89
- /// @notice Return the complete bounded command state without consuming it.
90
- /// @dev Remains the complete original state after the state cursor advances.
90
+ /// @notice Return the unread bounded command state without consuming it.
91
91
  function rawState(Execution memory exec) internal pure returns (bytes calldata) {
92
92
  return raw(exec, Lanes.State);
93
93
  }
94
94
 
95
- /// @notice Return the complete state lane and mark it fully consumed.
96
- /// @dev Intended for commands that forward their state intact without decoding it.
95
+ /// @notice Return the unread state lane and mark it fully consumed.
96
+ /// @dev Intended for commands that forward their remaining state without decoding it.
97
97
  /// A lane declared EMPTY is returned empty and remains unconsumed so close
98
98
  /// still rejects any state supplied against the descriptor.
99
99
  function takeRawState(Execution memory exec) internal pure returns (bytes calldata data) {
@@ -106,14 +106,13 @@ library Executions {
106
106
  exec.decoders = cur.seek(len);
107
107
  }
108
108
 
109
- /// @notice Return the complete bounded endpoint input without consuming it.
110
- /// @dev Remains the complete original input after the input cursor advances.
109
+ /// @notice Return the unread bounded endpoint input without consuming it.
111
110
  function rawInput(Execution memory exec) internal pure returns (bytes calldata) {
112
111
  return raw(exec, Lanes.Input);
113
112
  }
114
113
 
115
- /// @notice Return the complete input lane and mark it fully consumed.
116
- /// @dev Intended for endpoints that forward their input intact without decoding it.
114
+ /// @notice Return the unread input lane and mark it fully consumed.
115
+ /// @dev Intended for endpoints that forward their remaining input without decoding it.
117
116
  /// A lane declared EMPTY is returned empty and remains unconsumed so close
118
117
  /// still rejects any input supplied against the descriptor.
119
118
  function takeRawInput(Execution memory exec) internal pure returns (bytes calldata data) {
@@ -330,15 +329,6 @@ library Executions {
330
329
  node = Blocks.unpackNode(abs);
331
330
  }
332
331
 
333
- /// @notice Decode and consume one CASHOUT block from the active lane.
334
- /// @param exec Execution whose decoder is advanced.
335
- /// @return amount Native-asset amount to withdraw.
336
- function unpackCashout(Execution memory exec) internal pure returns (uint amount) {
337
- uint abs;
338
- (exec.decoders, abs) = exec.decoders.consume(Sizes.Cashout);
339
- amount = Blocks.unpackCashout(abs);
340
- }
341
-
342
332
  /// @notice Decode and consume one BOOTSTRAP block from the active lane.
343
333
  /// @param exec Execution whose decoder is advanced.
344
334
  /// @return asset Decoded asset identifier.
@@ -664,13 +654,12 @@ library Executions {
664
654
 
665
655
  /// @notice Decode and consume one RELAY block from the active lane.
666
656
  /// @param exec Execution whose decoder is advanced.
667
- /// @return portal Decoded destination portal.
668
- /// @return resources Decoded packed resources.
669
657
  /// @return input Decoded nested input.
670
- function unpackRelay(Execution memory exec) internal pure returns (uint portal, uint resources, bytes calldata input) {
658
+ /// @return steps Decoded remaining pipeline steps.
659
+ function unpackRelay(Execution memory exec) internal pure returns (bytes calldata input, bytes calldata steps) {
671
660
  uint cur = exec.decoders;
672
661
  uint end;
673
- (portal, resources, input, end) = Blocks.unpackRelay(cur.absolute());
662
+ (input, steps, end) = Blocks.unpackRelay(cur.absolute());
674
663
  exec.decoders = cur.seekAbs(end);
675
664
  }
676
665
 
@@ -1048,13 +1037,12 @@ library Executions {
1048
1037
 
1049
1038
  /// @notice Append a RELAY block to execution output.
1050
1039
  /// @param exec Execution receiving the block.
1051
- /// @param portal Destination portal to encode.
1052
- /// @param resources Packed resources to encode.
1053
1040
  /// @param input Relay input to encode.
1054
- function outputRelay(Execution memory exec, uint portal, uint resources, bytes memory input) internal pure {
1055
- uint size = Sizes.B64 + Sizes.Header + input.length;
1041
+ /// @param steps Remaining pipeline steps to encode.
1042
+ function outputRelay(Execution memory exec, bytes memory input, bytes memory steps) internal pure {
1043
+ uint size = 3 * Sizes.Header + input.length + steps.length;
1056
1044
  uint i = reserve(exec, size);
1057
- Blocks.writeRelay(exec.output, i, portal, resources, input);
1045
+ Blocks.writeRelay(exec.output, i, input, steps);
1058
1046
  }
1059
1047
 
1060
1048
  /// @notice Append a DISPATCH block to execution output.
@@ -1177,11 +1165,11 @@ library Executions {
1177
1165
  Blocks.copyCall(exec.output, i, target, resources, payload);
1178
1166
  }
1179
1167
 
1180
- /// @notice Append a RELAY block to execution output by copying its nested input from calldata.
1181
- function outputCopyRelay(Execution memory exec, uint portal, uint resources, bytes calldata input) internal pure {
1182
- uint size = Sizes.B64 + Sizes.Header + input.length;
1168
+ /// @notice Append a RELAY block to execution output by copying its nested streams from calldata.
1169
+ function outputCopyRelay(Execution memory exec, bytes calldata input, bytes calldata steps) internal pure {
1170
+ uint size = 3 * Sizes.Header + input.length + steps.length;
1183
1171
  uint i = reserve(exec, size);
1184
- Blocks.copyRelay(exec.output, i, portal, resources, input);
1172
+ Blocks.copyRelay(exec.output, i, input, steps);
1185
1173
  }
1186
1174
 
1187
1175
  /// @notice Append a DISPATCH block to execution output by copying its nested payload from calldata.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rootzero/contracts",
3
- "version": "1.28.0",
3
+ "version": "1.30.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",
@@ -2,34 +2,24 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import {PortBase} from "./Base.sol";
5
+ import {AllowanceHook} from "../commands/admin/Allowance.sol";
5
6
  import {Specs} from "../Codec.sol";
6
7
  import {Execution, Executions} from "../execution/Execution.sol";
7
8
 
8
9
  using Executions for Execution;
9
10
 
10
- /// @notice Hook implemented by hosts that handle allowance requests from peers.
11
- abstract contract RequestAllowanceHook {
12
- /// @notice Override to handle one allowance request from a peer host.
13
- /// @dev The implementation is responsible for validating the request and
14
- /// deciding what allowance, if any, to grant.
15
- /// @param peer Peer host node ID requesting the allowance.
16
- /// @param asset Asset identifier supplied by the peer.
17
- /// @param amount Allowance amount requested in the asset's native units.
18
- function requestAllowance(uint peer, bytes32 asset, uint amount) internal virtual;
19
- }
20
-
21
11
  /// @title RequestAllowancePort
22
- /// @notice Port that lets trusted peers request their own asset allowances.
23
- /// Each AMOUNT block is scoped to the caller and passed unchanged to
24
- /// `requestAllowance(peer, asset, amount)`.
25
- abstract contract RequestAllowancePort is PortBase, RequestAllowanceHook {
12
+ /// @notice Port that lets trusted peers set their own asset allowances.
13
+ /// Each AMOUNT block is scoped to the authenticated caller and applied through
14
+ /// the shared authoritative allowance hook.
15
+ abstract contract RequestAllowancePort is PortBase, AllowanceHook {
26
16
  uint private immutable descriptor;
27
17
 
28
18
  constructor() {
29
19
  (, descriptor) = port("portRequestAllowance", Specs.Amount, Specs.Empty, 0);
30
20
  }
31
21
 
32
- /// @notice Request asset allowances for the calling peer.
22
+ /// @notice Set asset allowances for the calling peer.
33
23
  /// @param data AMOUNT block stream supplied by the trusted peer.
34
24
  /// @return Empty response bytes.
35
25
  function portRequestAllowance(bytes calldata data) external onlyPeer returns (bytes memory) {
@@ -38,7 +28,7 @@ abstract contract RequestAllowancePort is PortBase, RequestAllowanceHook {
38
28
 
39
29
  while (exec.more()) {
40
30
  (bytes32 asset, uint amount) = exec.unpackAmount();
41
- requestAllowance(peer, asset, amount);
31
+ allowance(peer, asset, amount);
42
32
  }
43
33
 
44
34
  return "";
package/ports/Base.sol CHANGED
@@ -12,6 +12,9 @@ import { Descriptors } from "../codec/Descriptors.sol";
12
12
  /// @notice Abstract base for peer-facing rootzero ports.
13
13
  /// Ports handle inter-host operations between cooperating hosts.
14
14
  /// Access is restricted to trusted peer callers via `onlyPeer`.
15
+ /// @dev By convention, trusting a peer authorizes it to use every port exposed
16
+ /// by the host without an additional policy decision in each port. Hosts that
17
+ /// need narrower capabilities may enforce them in hooks or custom port bases.
15
18
  abstract contract PortBase is NodeCalls, NodeAccess, InputEndpointBase {
16
19
 
17
20
  /// @dev Restrict execution to trusted callers, excluding the commander.
@@ -55,7 +58,7 @@ abstract contract PortBase is NodeCalls, NodeAccess, InputEndpointBase {
55
58
  string memory name,
56
59
  uint descriptor
57
60
  ) internal returns (uint id, uint published) {
58
- id = Nodes.toPort(name, address(this));
61
+ id = Nodes.toPort(name, address(this), uint8(descriptor));
59
62
  published = endpoint(id, name, descriptor);
60
63
  }
61
64
  }
@@ -2,7 +2,7 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import {PortBase} from "./Base.sol";
5
- import {Flags} from "../codec/Descriptors.sol";
5
+ import {Flags} from "../utils/Flags.sol";
6
6
  import {Specs} from "../Codec.sol";
7
7
  import {Execution, Executions} from "../execution/Execution.sol";
8
8
 
package/ports/Pipe.sol CHANGED
@@ -2,7 +2,7 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import {PortBase} from "./Base.sol";
5
- import {Flags} from "../codec/Descriptors.sol";
5
+ import {Flags} from "../utils/Flags.sol";
6
6
  import {PipeHook} from "../core/Pipeline.sol";
7
7
  import {Specs} from "../Codec.sol";
8
8
  import {Execution, Executions} from "../execution/Execution.sol";
package/queries/Base.sol CHANGED
@@ -38,7 +38,7 @@ abstract contract QueryBase is InputEndpointBase {
38
38
  string memory name,
39
39
  uint descriptor
40
40
  ) internal returns (uint id, uint published) {
41
- id = Nodes.toQuery(name, address(this));
41
+ id = Nodes.toQuery(name, address(this), uint8(descriptor));
42
42
  published = endpoint(id, name, descriptor);
43
43
  }
44
44
  }
@@ -19,12 +19,12 @@ import {ensureAddr, isFamily, toLocalBase, toUnspecifiedBase} from "./Utils.sol"
19
19
  ///
20
20
  /// The helpers in this library validate and deconstruct structured account IDs.
21
21
  library Accounts {
22
- /// @dev 24-bit family tag shared by all EVM-backed account types.
23
- uint24 constant Family = (uint24(Layout.Evm) << 8) | uint24(Layout.Account);
22
+ /// @dev 16-bit family tag shared by all EVM-backed account types.
23
+ uint16 constant Family = (uint16(Layout.Evm) << 8) | uint16(Layout.Account);
24
24
  /// @dev Full 4-byte type prefix for admin accounts (chain-local EVM address).
25
- uint32 constant Admin = (uint32(Layout.Evm) << 16) | (uint32(Layout.Account) << 8) | uint32(Layout.Admin);
25
+ uint32 constant Admin = (uint32(Layout.Evm) << 24) | (uint32(Layout.Account) << 16) | (uint32(Layout.Admin) << 8);
26
26
  /// @dev Full 4-byte type prefix for user accounts (chain-agnostic EVM address).
27
- uint32 constant User = (uint32(Layout.Evm) << 16) | (uint32(Layout.Account) << 8) | uint32(Layout.User);
27
+ uint32 constant User = (uint32(Layout.Evm) << 24) | (uint32(Layout.Account) << 16) | (uint32(Layout.User) << 8);
28
28
 
29
29
  /// @notice Extract the 4-byte type prefix from an account ID.
30
30
  /// @param account Account identifier.
package/utils/Assets.sol CHANGED
@@ -21,12 +21,12 @@ import {ensureAddr, isFamily, matchesBase, toLocalBase} from "./Utils.sol";
21
21
  ///
22
22
  /// All asset IDs are chain-local (include `block.chainid` in bits [223:192]).
23
23
  library Assets {
24
- /// @dev 24-bit family tag shared by all EVM-backed asset types.
25
- uint24 constant Family = (uint24(Layout.Evm) << 8) | uint24(Layout.Asset);
24
+ /// @dev 16-bit family tag shared by all EVM-backed asset types.
25
+ uint16 constant Family = (uint16(Layout.Evm) << 8) | uint16(Layout.Asset);
26
26
  /// @dev Full 4-byte type prefix for the native chain coin/token asset.
27
- uint32 constant Native = (uint32(Layout.Evm) << 16) | (uint32(Layout.Asset) << 8) | uint32(Layout.Native);
27
+ uint32 constant Native = (uint32(Layout.Evm) << 24) | (uint32(Layout.Asset) << 16) | (uint32(Layout.Native) << 8);
28
28
  /// @dev Full 4-byte type prefix for ERC-20 assets.
29
- uint32 constant Erc20 = (uint32(Layout.Evm) << 16) | (uint32(Layout.Asset) << 8) | uint32(Layout.Erc20);
29
+ uint32 constant Erc20 = (uint32(Layout.Evm) << 24) | (uint32(Layout.Asset) << 16) | (uint32(Layout.Erc20) << 8);
30
30
 
31
31
  /// @notice Return true if `asset` belongs to the EVM asset family.
32
32
  function isEvm(bytes32 asset) internal pure returns (bool) {
@@ -126,7 +126,6 @@ library Assets {
126
126
  if (erc20Addr(asset) != token) revert InvalidAsset();
127
127
  return asset;
128
128
  }
129
-
130
129
  }
131
130
 
132
131
  /// @title Amounts
package/utils/Cursors.sol CHANGED
@@ -81,7 +81,30 @@ library Cursors {
81
81
  end = abs + uint32(cur >> 64);
82
82
  }
83
83
 
84
- /// @notice Create a cursor backed by a calldata slice.
84
+ /// @notice Return the unread calldata region represented by the lower cursor.
85
+ /// @dev DANGER: This trusts the packed cursor and does not validate the frame
86
+ /// against `msg.data`. Assumes the cursor invariant `i <= len`; any upper
87
+ /// cursor is ignored.
88
+ /// @param cur Packed cursor or cursor pair whose unread region is returned.
89
+ /// @return data Lower-cursor calldata from its current position through its end.
90
+ function raw(uint cur) internal pure returns (bytes calldata data) {
91
+ assembly ("memory-safe") {
92
+ let i := and(cur, 0xffffffff)
93
+ data.offset := add(and(shr(32, cur), 0xffffffff), i)
94
+ data.length := sub(and(shr(64, cur), 0xffffffff), i)
95
+ }
96
+ }
97
+
98
+ /// @notice Create an untagged cursor backed by a calldata slice.
99
+ /// @param source Calldata slice represented by the cursor.
100
+ /// @return cur Packed cursor positioned at the slice beginning.
101
+ function wrap(bytes calldata source) internal pure returns (uint cur) {
102
+ assembly ("memory-safe") {
103
+ cur := or(shl(32, source.offset), shl(64, source.length))
104
+ }
105
+ }
106
+
107
+ /// @notice Create a tagged cursor backed by a calldata slice.
85
108
  /// @param source Calldata slice represented by the cursor.
86
109
  /// @param tag Cursor identity tag.
87
110
  /// @return cur Packed cursor positioned at the slice beginning.
@@ -0,0 +1,18 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ /// @title Flags
5
+ /// @notice Shared endpoint behavior flags encoded in both endpoint IDs and descriptors.
6
+ /// @dev Bit 6 is reserved for endpoint-defined custom behavior.
7
+ library Flags {
8
+ /// @dev Endpoint accepts nonzero native value.
9
+ uint8 internal constant Funded = 1 << 0;
10
+ /// @dev Endpoint is restricted to the admin account.
11
+ uint8 internal constant Admin = 1 << 1;
12
+ /// @dev Endpoint accepts nonzero native value and is restricted to the admin account.
13
+ uint8 internal constant AdminFunded = Admin | Funded;
14
+ /// @dev Endpoint takes ownership of the remaining pipeline steps.
15
+ uint8 internal constant Handoff = 1 << 7;
16
+ /// @dev Endpoint accepts nonzero native value and takes ownership of the remaining pipeline steps.
17
+ uint8 internal constant HandoffFunded = Handoff | Funded;
18
+ }
package/utils/Layout.sol CHANGED
@@ -7,7 +7,7 @@ pragma solidity ^0.8.33;
7
7
  ///
8
8
  /// IDs are structured as:
9
9
  /// `[uint32 type][uint32 chainid][192-bit payload]`
10
- /// where `type` is `[uint16 representation][uint8 category][uint8 subtype]`.
10
+ /// where `type` is `[uint8 representation][uint8 category][uint8 subtype][uint8 flags]`.
11
11
  ///
12
12
  /// Values whose first byte is zero are opaque IDs:
13
13
  /// `[0x00][bytes31 truncated hash]`
@@ -17,11 +17,11 @@ pragma solidity ^0.8.33;
17
17
  /// Values whose first byte is nonzero follow the structured layout above.
18
18
  library Layout {
19
19
  // -------------------------------------------------------------------------
20
- // Representation tags (top 2 bytes of the ID type field)
20
+ // Representation tags (first byte of the ID type field)
21
21
  // -------------------------------------------------------------------------
22
22
 
23
23
  /// @dev EVM-compatible ID; lower 20 payload bytes hold an address when present.
24
- uint16 constant Evm = 0x0120;
24
+ uint8 constant Evm = 0x01;
25
25
 
26
26
  // -------------------------------------------------------------------------
27
27
  // Category tags (uint8, third byte of the ID type field)
package/utils/Nodes.sol CHANGED
@@ -6,21 +6,6 @@ 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
-
24
9
  /// @title Nodes
25
10
  /// @notice Encoding and decoding helpers for 256-bit node identifiers.
26
11
  ///
@@ -36,20 +21,20 @@ function unpackCommand(uint cmd) pure returns (bytes4 selector, address target)
36
21
  ///
37
22
  /// The helpers in this library validate and deconstruct structured node IDs.
38
23
  library Nodes {
39
- /// @dev 24-bit family tag shared by all node types (Evm + Node category).
40
- uint24 constant Family = (uint24(Layout.Evm) << 8) | uint24(Layout.Node);
24
+ /// @dev 16-bit family tag shared by all node types (Evm + Node category).
25
+ uint16 constant Family = (uint16(Layout.Evm) << 8) | uint16(Layout.Node);
41
26
  /// @dev Full 4-byte type prefix for chain/domain nodes.
42
- uint32 constant Chain = (uint32(Layout.Evm) << 16) | (uint32(Layout.Node) << 8) | uint32(Layout.Chain);
27
+ uint32 constant Chain = (uint32(Layout.Evm) << 24) | (uint32(Layout.Node) << 16) | (uint32(Layout.Chain) << 8);
43
28
  /// @dev Full 4-byte type prefix for host nodes.
44
- uint32 constant Host = (uint32(Layout.Evm) << 16) | (uint32(Layout.Node) << 8) | uint32(Layout.Host);
29
+ uint32 constant Host = (uint32(Layout.Evm) << 24) | (uint32(Layout.Node) << 16) | (uint32(Layout.Host) << 8);
45
30
  /// @dev Full 4-byte type prefix for command nodes.
46
- uint32 constant Command = (uint32(Layout.Evm) << 16) | (uint32(Layout.Node) << 8) | uint32(Layout.Command);
31
+ uint32 constant Command = (uint32(Layout.Evm) << 24) | (uint32(Layout.Node) << 16) | (uint32(Layout.Command) << 8);
47
32
  /// @dev Full 4-byte type prefix for port nodes.
48
- uint32 constant Port = (uint32(Layout.Evm) << 16) | (uint32(Layout.Node) << 8) | uint32(Layout.Port);
33
+ uint32 constant Port = (uint32(Layout.Evm) << 24) | (uint32(Layout.Node) << 16) | (uint32(Layout.Port) << 8);
49
34
  /// @dev Full 4-byte type prefix for query nodes.
50
- uint32 constant Query = (uint32(Layout.Evm) << 16) | (uint32(Layout.Node) << 8) | uint32(Layout.Query);
35
+ uint32 constant Query = (uint32(Layout.Evm) << 24) | (uint32(Layout.Node) << 16) | (uint32(Layout.Query) << 8);
51
36
  /// @dev Full 4-byte type prefix for guard action nodes.
52
- uint32 constant Guard = (uint32(Layout.Evm) << 16) | (uint32(Layout.Node) << 8) | uint32(Layout.Guard);
37
+ uint32 constant Guard = (uint32(Layout.Evm) << 24) | (uint32(Layout.Node) << 16) | (uint32(Layout.Guard) << 8);
53
38
 
54
39
  /// @notice Return true if `node` is a host node ID.
55
40
  function isHost(uint node) internal pure returns (bool) {
@@ -58,22 +43,22 @@ library Nodes {
58
43
 
59
44
  /// @notice Return true if `node` is a command node ID.
60
45
  function isCommand(uint node) internal pure returns (bool) {
61
- return uint32(node >> 224) == Command;
46
+ return (uint32(node >> 224) & 0xffffff00) == Command;
62
47
  }
63
48
 
64
49
  /// @notice Return true if `node` is a port node ID.
65
50
  function isPort(uint node) internal pure returns (bool) {
66
- return uint32(node >> 224) == Port;
51
+ return (uint32(node >> 224) & 0xffffff00) == Port;
67
52
  }
68
53
 
69
54
  /// @notice Return true if `node` is a query node ID.
70
55
  function isQuery(uint node) internal pure returns (bool) {
71
- return uint32(node >> 224) == Query;
56
+ return (uint32(node >> 224) & 0xffffff00) == Query;
72
57
  }
73
58
 
74
59
  /// @notice Return true if `node` is a guard action node ID.
75
60
  function isGuard(uint node) internal pure returns (bool) {
76
- return uint32(node >> 224) == Guard;
61
+ return (uint32(node >> 224) & 0xffffff00) == Guard;
77
62
  }
78
63
 
79
64
  /// @notice Return true if `node` belongs to the EVM node family.
@@ -210,7 +195,12 @@ library Nodes {
210
195
  /// @param target Command contract address.
211
196
  /// @return node Command node ID embedding both the selector and address.
212
197
  function toCommand(string memory name, address target) internal view returns (uint node) {
213
- node = toLocalBase(Command) | uint(uint160(target));
198
+ return toCommand(name, target, 0);
199
+ }
200
+
201
+ /// @notice Build a chain-local command ID carrying endpoint behavior flags.
202
+ function toCommand(string memory name, address target, uint8 flags) internal view returns (uint node) {
203
+ node = toLocalBase(Command | uint32(flags)) | uint(uint160(target));
214
204
  node |= uint(uint32(toSelector(name, "(bytes)"))) << 160;
215
205
  }
216
206
 
@@ -219,7 +209,12 @@ library Nodes {
219
209
  /// @param target Port contract address.
220
210
  /// @return node Port node ID embedding both the selector and address.
221
211
  function toPort(string memory name, address target) internal view returns (uint node) {
222
- node = toLocalBase(Port) | uint(uint160(target));
212
+ return toPort(name, target, 0);
213
+ }
214
+
215
+ /// @notice Build a chain-local port ID carrying endpoint behavior flags.
216
+ function toPort(string memory name, address target, uint8 flags) internal view returns (uint node) {
217
+ node = toLocalBase(Port | uint32(flags)) | uint(uint160(target));
223
218
  node |= uint(uint32(toSelector(name, "(bytes)"))) << 160;
224
219
  }
225
220
 
@@ -228,7 +223,12 @@ library Nodes {
228
223
  /// @param target Query contract address.
229
224
  /// @return node Query node ID embedding both the selector and address.
230
225
  function toQuery(string memory name, address target) internal view returns (uint node) {
231
- node = toLocalBase(Query) | uint(uint160(target));
226
+ return toQuery(name, target, 0);
227
+ }
228
+
229
+ /// @notice Build a chain-local query ID carrying endpoint behavior flags.
230
+ function toQuery(string memory name, address target, uint8 flags) internal view returns (uint node) {
231
+ node = toLocalBase(Query | uint32(flags)) | uint(uint160(target));
232
232
  node |= uint(uint32(toSelector(name, "(bytes)"))) << 160;
233
233
  }
234
234
 
@@ -237,7 +237,12 @@ library Nodes {
237
237
  /// @param target Guard action contract address.
238
238
  /// @return node Guard action node ID embedding both the selector and address.
239
239
  function toGuard(string memory name, address target) internal view returns (uint node) {
240
- node = toLocalBase(Guard) | uint(uint160(target));
240
+ return toGuard(name, target, 0);
241
+ }
242
+
243
+ /// @notice Build a chain-local guard ID carrying endpoint behavior flags.
244
+ function toGuard(string memory name, address target, uint8 flags) internal view returns (uint node) {
245
+ node = toLocalBase(Guard | uint32(flags)) | uint(uint160(target));
241
246
  node |= uint(uint32(toSelector(name, "(bytes)"))) << 160;
242
247
  }
243
248
 
package/utils/Utils.sol CHANGED
@@ -207,13 +207,13 @@ function toUnspecifiedBase(uint32 prefix) pure returns (uint) {
207
207
  return uint(prefix) << 224;
208
208
  }
209
209
 
210
- /// @notice Check whether `value` belongs to the given 24-bit family.
211
- /// Only tests the top 3 bytes (bits [255:232]); does not check chainid.
210
+ /// @notice Check whether `value` belongs to the given 16-bit representation/category family.
211
+ /// Only tests the top 2 bytes (bits [255:240]); does not check subtype, flags, or chainid.
212
212
  /// @param value ID to test.
213
213
  /// @param family Expected family tag.
214
- /// @return True if the top 3 bytes of `value` match `family`.
215
- function isFamily(uint value, uint24 family) pure returns (bool) {
216
- return uint24(value >> 232) == family;
214
+ /// @return True if the top 2 bytes of `value` match `family`.
215
+ function isFamily(uint value, uint16 family) pure returns (bool) {
216
+ return uint16(value >> 240) == family;
217
217
  }
218
218
 
219
219
  /// @notice Check whether two IDs share the same 64-bit base (type tag + chainid).