@rootzero/contracts 1.13.0 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/CHANGELOG.md +87 -3
  2. package/Codec.sol +21 -0
  3. package/Commands.sol +14 -0
  4. package/Core.sol +8 -6
  5. package/Endpoints.sol +2 -7
  6. package/Events.sol +1 -2
  7. package/README.md +70 -37
  8. package/Utils.sol +2 -4
  9. package/annotations/Action.sol +17 -0
  10. package/annotations/Label.sol +23 -0
  11. package/annotations/Schema.sol +53 -0
  12. package/codec/Blocks.sol +1643 -0
  13. package/codec/Buffers.sol +165 -0
  14. package/codec/Decoders.sol +570 -0
  15. package/codec/Descriptors.sol +124 -0
  16. package/{blocks → codec}/Keys.sol +10 -18
  17. package/codec/Readers.sol +114 -0
  18. package/{blocks → codec}/Schema.sol +43 -80
  19. package/codec/Specs.sol +240 -0
  20. package/codec/Writers.sol +485 -0
  21. package/commands/Allocate.sol +21 -20
  22. package/commands/Base.sol +83 -45
  23. package/commands/Burn.sol +23 -14
  24. package/commands/Credit.sol +21 -20
  25. package/commands/Debit.sol +25 -28
  26. package/commands/Deposit.sol +41 -36
  27. package/commands/Payout.sol +26 -16
  28. package/commands/Provision.sol +37 -38
  29. package/commands/Recover.sol +20 -19
  30. package/commands/Relay.sol +24 -22
  31. package/commands/Withdraw.sol +20 -19
  32. package/commands/admin/AllowAssets.sol +19 -16
  33. package/commands/admin/Allowance.sol +18 -13
  34. package/commands/admin/Annotate.sol +36 -0
  35. package/commands/admin/Appoint.sol +19 -16
  36. package/commands/admin/Authorize.sol +23 -14
  37. package/commands/admin/Base.sol +9 -2
  38. package/commands/admin/DenyAssets.sol +19 -16
  39. package/commands/admin/Dismiss.sol +19 -16
  40. package/commands/admin/Execute.sol +21 -19
  41. package/commands/admin/Unauthorize.sol +23 -14
  42. package/core/Access.sol +91 -49
  43. package/core/Calls.sol +30 -33
  44. package/core/Endpoint.sol +46 -143
  45. package/core/Host.sol +63 -21
  46. package/core/Pipeline.sol +16 -15
  47. package/core/Types.sol +1 -1
  48. package/docs/Schema.md +48 -31
  49. package/events/Annotation.sol +24 -0
  50. package/events/Endpoint.sol +2 -2
  51. package/events/Guardian.sol +2 -2
  52. package/events/Introduction.sol +3 -2
  53. package/execution/Budget.sol +40 -0
  54. package/execution/Execution.sol +1104 -0
  55. package/guards/Base.sol +11 -12
  56. package/guards/Revoke.sol +11 -9
  57. package/package.json +1 -1
  58. package/ports/AllowAssets.sol +12 -15
  59. package/ports/Allowance.sol +11 -9
  60. package/ports/Base.sol +35 -16
  61. package/ports/Credit.sol +11 -9
  62. package/ports/Debit.sol +11 -9
  63. package/ports/DenyAssets.sol +10 -13
  64. package/ports/Dispatch.sol +13 -15
  65. package/ports/Pipe.sol +14 -12
  66. package/ports/Redeem.sol +12 -9
  67. package/ports/Settle.sol +16 -10
  68. package/queries/Assets.sol +15 -15
  69. package/queries/Balances.sol +17 -17
  70. package/queries/Base.sol +26 -12
  71. package/utils/Accounts.sol +0 -23
  72. package/utils/Actions.sol +1 -0
  73. package/utils/Cursors.sol +367 -0
  74. package/utils/Lanes.sol +14 -0
  75. package/utils/Layout.sol +0 -2
  76. package/utils/Selectors.sol +2 -2
  77. package/utils/Utils.sol +46 -0
  78. package/Cursors.sol +0 -16
  79. package/blocks/Cursors.sol +0 -1529
  80. package/blocks/Writers.sol +0 -1036
  81. package/commands/admin/Label.sol +0 -32
  82. package/commands/admin/Schemas.sol +0 -32
  83. package/core/Payable.sol +0 -53
  84. package/events/Labeled.sol +0 -21
  85. package/events/Schema.sol +0 -23
  86. package/utils/Value.sol +0 -43
@@ -1,12 +1,13 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import {Cur, Cursors, Keys, Writer, Writers} from "../Cursors.sol";
4
+ import {Specs} from "../Codec.sol";
5
+ import {Execution, Executions, Lanes} from "../execution/Execution.sol";
5
6
  import {QueryBase} from "./Base.sol";
6
7
 
7
- using Cursors for Cur;
8
- using Writers for Writer;
8
+ using Executions for Execution;
9
9
 
10
+ /// @notice Hook implemented by hosts that expose asset status queries.
10
11
  abstract contract AssetStatusHook {
11
12
  /// @notice Resolve support status for one asset.
12
13
  /// Concrete implementations define the support policy and optional context codes.
@@ -17,28 +18,27 @@ abstract contract AssetStatusHook {
17
18
 
18
19
  /// @title AssetStatus
19
20
  /// @notice Rootzero query that checks support status for one or more assets.
20
- /// The request is a run of `ASSET` blocks.
21
- /// The response returns one `STATUS` form block per query entry, preserving request order.
21
+ /// The input is a run of `ASSET` blocks.
22
+ /// The response returns one `STATUS` form block per query entry, preserving input order.
22
23
  abstract contract AssetStatus is QueryBase, AssetStatusHook {
23
- bytes32 private immutable descriptor;
24
+ uint private immutable descriptor;
24
25
 
25
26
  constructor() {
26
- (, descriptor) = query("assetStatus", Keys.Asset, Keys.Status, 0);
27
+ (, descriptor) = query("assetStatus", Specs.Asset, Specs.Status);
27
28
  }
28
29
 
29
30
  /// @notice Resolve asset support status for a run of requested assets.
30
- /// @param request Block-stream request consisting of `asset { bytes32 asset }` blocks.
31
+ /// @param input Block-stream input consisting of `asset { bytes32 asset }` blocks.
31
32
  /// @return Block-stream response containing one `status { uint code }` form block per asset block.
32
- function assetStatus(bytes calldata request) external view returns (bytes memory) {
33
- (Cur memory input, uint outputs) = openInput(request, descriptor);
34
- Writer memory response = Writers.allocStatuses(outputs);
33
+ function assetStatus(bytes calldata input) external view returns (bytes memory) {
34
+ Execution memory exec = openInput(input, descriptor, 0);
35
35
 
36
- while (input.i < input.len) {
37
- bytes32 asset = input.unpackAsset();
36
+ while (exec.more()) {
37
+ bytes32 asset = exec.unpackAsset(Lanes.Input);
38
38
  uint status = assetStatus(asset);
39
- response.appendStatus(status);
39
+ exec.outputStatus(status);
40
40
  }
41
41
 
42
- return response.finish();
42
+ return close(exec);
43
43
  }
44
44
  }
@@ -1,12 +1,13 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import {Cur, Cursors, Keys, Writer, Writers} from "../Cursors.sol";
4
+ import {Specs} from "../Codec.sol";
5
+ import {Execution, Executions, Lanes} from "../execution/Execution.sol";
5
6
  import {QueryBase} from "./Base.sol";
6
7
 
7
- using Cursors for Cur;
8
- using Writers for Writer;
8
+ using Executions for Execution;
9
9
 
10
+ /// @notice Hook implemented by hosts that expose account balance queries.
10
11
  abstract contract GetBalancesHook {
11
12
  /// @notice Resolve one account's balance for one supported asset.
12
13
  /// Concrete implementations define how assets are resolved.
@@ -18,28 +19,27 @@ abstract contract GetBalancesHook {
18
19
 
19
20
  /// @title GetBalances
20
21
  /// @notice Rootzero query that resolves balances for one or more `(account, asset)` tuples.
21
- /// The request is a run of `ACCOUNT_ASSET` form blocks.
22
- /// The response returns one `ACCOUNT_AMOUNT` form block per requested position, preserving request order.
22
+ /// The input is a run of `ACCOUNT_ASSET` form blocks.
23
+ /// The response returns one `ACCOUNT_AMOUNT` form block per requested position, preserving input order.
23
24
  abstract contract GetBalances is QueryBase, GetBalancesHook {
24
- bytes32 private immutable descriptor;
25
+ uint private immutable descriptor;
25
26
 
26
27
  constructor() {
27
- (, descriptor) = query("getBalances", Keys.AccountAsset, Keys.AccountAmount, 0);
28
+ (, descriptor) = query("getBalances", Specs.AccountAsset, Specs.AccountAmount);
28
29
  }
29
30
 
30
31
  /// @notice Resolve balances for a run of requested `(account, asset)` tuples.
31
- /// @param request Block-stream request consisting of `accountAsset(account, asset)*`.
32
- /// @return Block-stream response containing one `accountAmount(account, asset, amount)` block per request block.
33
- function getBalances(bytes calldata request) external view returns (bytes memory) {
34
- (Cur memory input, uint outputs) = openInput(request, descriptor);
35
- Writer memory response = Writers.allocAccountAmounts(outputs);
36
-
37
- while (input.i < input.len) {
38
- (bytes32 account, bytes32 asset) = input.unpackAccountAsset();
32
+ /// @param input Block-stream input consisting of `accountAsset(account, asset)*`.
33
+ /// @return Block-stream response containing one `accountAmount(account, asset, amount)` block per input block.
34
+ function getBalances(bytes calldata input) external view returns (bytes memory) {
35
+ Execution memory exec = openInput(input, descriptor, 0);
36
+
37
+ while (exec.more()) {
38
+ (bytes32 account, bytes32 asset) = exec.unpackAccountAsset(Lanes.Input);
39
39
  uint amount = getBalance(account, asset);
40
- response.appendAccountAmount(account, asset, amount);
40
+ exec.outputAccountAmount(account, asset, amount);
41
41
  }
42
42
 
43
- return response.finish();
43
+ return close(exec);
44
44
  }
45
45
  }
package/queries/Base.sol CHANGED
@@ -2,30 +2,44 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import { EndpointBase } from "../core/Endpoint.sol";
5
+ import { Descriptors } from "../codec/Descriptors.sol";
6
+ import { Specs } from "../codec/Specs.sol";
5
7
  import { Nodes } from "../utils/Nodes.sol";
6
8
  import { Selectors } from "../utils/Selectors.sol";
7
9
 
8
10
  /// @title QueryBase
9
11
  /// @notice Abstract base for rootzero query contracts.
10
- /// Queries are view-only entry points that consume a block-stream request and
12
+ /// Queries are view-only entry points that consume a block-stream input and
11
13
  /// return a block-stream response.
12
14
  abstract contract QueryBase is EndpointBase {
13
15
 
14
16
  /// @notice Publish query metadata and a default label.
15
- /// @param name Default human-readable query label and selector name.
16
- /// @param input Packed input lane plus optional group byte.
17
- /// @param output Packed output lane plus optional group byte.
18
- /// @param selector Query ABI selector, or zero to derive it from `name`.
17
+ /// @param name Query entrypoint name and default label. It must exactly
18
+ /// match the Solidity query function name used by the canonical ABI.
19
+ /// @param input Input block specification.
20
+ /// @param output Output block specification.
19
21
  /// @return id Query node ID.
20
22
  /// @return descriptor Packed endpoint lane metadata and flags.
21
23
  function query(
22
24
  string memory name,
23
- bytes9 input,
24
- bytes9 output,
25
- bytes4 selector
26
- ) internal returns (uint id, bytes32 descriptor) {
27
- selector = selector == bytes4(0) ? Selectors.query(name) : selector;
28
- id = Nodes.toQuery(selector, address(this));
29
- descriptor = endpoint(id, name, bytes9(0), input, output, false, false);
25
+ uint input,
26
+ uint output
27
+ ) internal returns (uint id, uint descriptor) {
28
+ descriptor = Descriptors.create(Specs.Empty, input, output, 0, 0);
29
+ return query(name, descriptor);
30
+ }
31
+
32
+ /// @notice Publish an already constructed query descriptor and default label.
33
+ /// @param name Query entrypoint name and default label. It must exactly
34
+ /// match the Solidity query function name used by the canonical ABI.
35
+ /// @param descriptor Packed query endpoint descriptor.
36
+ /// @return id Query node ID.
37
+ /// @return published Published endpoint descriptor.
38
+ function query(
39
+ string memory name,
40
+ uint descriptor
41
+ ) internal returns (uint id, uint published) {
42
+ id = Nodes.toQuery(Selectors.query(name), address(this));
43
+ published = endpoint(id, name, descriptor);
30
44
  }
31
45
  }
@@ -10,7 +10,6 @@ import {ensureAddr, isFamily, toLocalBase, toUnspecifiedBase} from "./Utils.sol"
10
10
  ///
11
11
  /// Account IDs embed a 4-byte type tag in bits [255:224]:
12
12
  /// - `Admin` — chain-local EVM address in bits [191:32]
13
- /// - `Guardian` — chain-local EVM address in bits [191:32]
14
13
  /// - `User` — chain-agnostic EVM address in bits [191:32]
15
14
  ///
16
15
  /// If the first byte is zero, the account is an opaque
@@ -26,8 +25,6 @@ library Accounts {
26
25
  uint24 constant Family = (uint24(Layout.Evm) << 8) | uint24(Layout.Account);
27
26
  /// @dev Full 4-byte type prefix for admin accounts (chain-local EVM address).
28
27
  uint32 constant Admin = (uint32(Layout.Evm) << 16) | (uint32(Layout.Account) << 8) | uint32(Layout.Admin);
29
- /// @dev Full 4-byte type prefix for guardian accounts (chain-local EVM address).
30
- uint32 constant Guardian = (uint32(Layout.Evm) << 16) | (uint32(Layout.Account) << 8) | uint32(Layout.Guardian);
31
28
  /// @dev Full 4-byte type prefix for user accounts (chain-agnostic EVM address).
32
29
  uint32 constant User = (uint32(Layout.Evm) << 16) | (uint32(Layout.Account) << 8) | uint32(Layout.User);
33
30
 
@@ -53,11 +50,6 @@ library Accounts {
53
50
  return prefix(account) == Admin;
54
51
  }
55
52
 
56
- /// @notice Return true if `account` is a guardian account.
57
- function isGuardian(bytes32 account) internal pure returns (bool) {
58
- return prefix(account) == Guardian;
59
- }
60
-
61
53
  /// @notice Return true if `account` is a user account.
62
54
  function isUser(bytes32 account) internal pure returns (bool) {
63
55
  return prefix(account) == User;
@@ -87,14 +79,6 @@ library Accounts {
87
79
  return value;
88
80
  }
89
81
 
90
- /// @notice Assert that `value` is a guardian account and return it unchanged.
91
- /// @param value Account identifier to validate.
92
- /// @return account The same `value` if it is a guardian account.
93
- function guardian(bytes32 value) internal pure returns (bytes32 account) {
94
- if (!isGuardian(value)) revert InvalidAccount();
95
- return value;
96
- }
97
-
98
82
  /// @notice Assert that `value` is a user account and return it unchanged.
99
83
  /// @param value Account identifier to validate.
100
84
  /// @return account The same `value` if it is a user account.
@@ -110,13 +94,6 @@ library Accounts {
110
94
  return bytes32(toLocalBase(Admin) | (uint(uint160(account)) << 32));
111
95
  }
112
96
 
113
- /// @notice Encode an EVM address as a chain-local guardian account ID.
114
- /// @param account EVM address to embed.
115
- /// @return Guardian account ID bound to the current chain.
116
- function toGuardian(address account) internal view returns (bytes32) {
117
- return bytes32(toLocalBase(Guardian) | (uint(uint160(account)) << 32));
118
- }
119
-
120
97
  /// @notice Encode an EVM address as a chain-agnostic user account ID.
121
98
  /// @param account EVM address to embed.
122
99
  /// @return User account ID without a chain binding.
package/utils/Actions.sol CHANGED
@@ -1,6 +1,7 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
+ /// @notice Canonical action identifiers used by protocol events.
4
5
  library Actions {
5
6
  uint32 constant None = 0;
6
7
  uint32 constant Transfer = 1;
@@ -0,0 +1,367 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import {clear32, max16, max32, max128} from "./Utils.sol";
5
+
6
+ /// @notice Mutable memory wrapper around a packed cursor.
7
+ /// @dev A second tagged cursor may occupy the upper 128-bit lane of `state`.
8
+ /// Cursor operations target the lower lane; `Cursors.select` swaps the requested
9
+ /// tagged cursor into that position.
10
+ struct Cur {
11
+ uint state;
12
+ }
13
+
14
+ /// @title Cursors
15
+ /// @notice Packed cursor state and navigation for grouped byte regions.
16
+ /// @dev Each 128-bit cursor uses the following layout:
17
+ /// bits 0-31 i
18
+ /// bits 32-63 offset
19
+ /// bits 64-95 len
20
+ /// bits 96-111 groups
21
+ /// bits 112-119 flags (consumer-defined)
22
+ /// bits 120-127 tag
23
+ ///
24
+ /// A cursor is one 128-bit value with this layout. A pair is a 256-bit value
25
+ /// containing a lower cursor in bits 0-127 and an optional higher cursor in
26
+ /// bits 128-255. The lower cursor is the active cursor: navigation and
27
+ /// inspection operations target it, and `select` swaps a requested tagged
28
+ /// cursor into that position while preserving the pair.
29
+ ///
30
+ /// A mark is a standalone cursor value used as an immutable positional
31
+ /// reference. It retains the cursor's offset, length, groups, flags, and tag,
32
+ /// but may carry a different `i`. A mark has no intrinsic boundary or movement
33
+ /// semantics; callers may later use its position for comparison, validation,
34
+ /// seeking, or another operation. Because it has the ordinary single-cursor
35
+ /// layout, existing positional decoding and absolute-position rules also apply
36
+ /// to marks. A zero mark identifies the empty cursor at position zero; `before`
37
+ /// therefore treats it as already reached.
38
+ ///
39
+ /// The zero word represents an absent cursor.
40
+ library Cursors {
41
+ /// @dev A cursor position exceeds its logical length.
42
+ error OutOfBounds();
43
+
44
+ /// @dev Two optional group counts are both set but do not match.
45
+ error BadRatio();
46
+
47
+ /// @dev A cursor is not positioned at the expected offset.
48
+ error UnexpectedPosition();
49
+
50
+ /// @dev Paired cursors must have different identity tags.
51
+ error DuplicateTag(uint8 tag);
52
+
53
+ /// @dev Neither packed cursor matches the requested identity.
54
+ error MissingCursor();
55
+
56
+ // Creation and sources
57
+
58
+ /// @notice Create a cursor positioned at its beginning.
59
+ /// @param offset Absolute source or buffer offset.
60
+ /// @param len Logical byte length.
61
+ /// @param groups Logical group count.
62
+ /// @param flags Consumer-defined flags.
63
+ /// @param tag Cursor identity tag.
64
+ /// @return cur Packed cursor.
65
+ function create(uint offset, uint len, uint groups, uint8 flags, uint8 tag) internal pure returns (uint cur) {
66
+ cur |= max32(offset) << 32;
67
+ cur |= max32(len) << 64;
68
+ cur |= max16(groups) << 96;
69
+ cur |= uint(flags) << 112;
70
+ cur |= uint(tag) << 120;
71
+ }
72
+
73
+ /// @notice Return the absolute calldata position where `source` begins.
74
+ /// @param source Calldata slice whose base is requested.
75
+ /// @return abs Absolute calldata position.
76
+ function base(bytes calldata source) internal pure returns (uint abs) {
77
+ assembly ("memory-safe") {
78
+ abs := source.offset
79
+ }
80
+ }
81
+
82
+ /// @notice Return the absolute calldata start and exclusive end of `source`.
83
+ /// @param source Calldata slice whose bounds are requested.
84
+ /// @return abs Absolute start position.
85
+ /// @return end Absolute exclusive end position.
86
+ function bounds(bytes calldata source) internal pure returns (uint abs, uint end) {
87
+ abs = base(source);
88
+ end = abs + source.length;
89
+ }
90
+
91
+ /// @notice Create a cursor backed by a calldata slice.
92
+ /// @param source Calldata slice represented by the cursor.
93
+ /// @param flags Consumer-defined flags.
94
+ /// @param tag Cursor identity tag.
95
+ /// @return cur Packed cursor positioned at the slice beginning.
96
+ function wrap(bytes calldata source, uint8 flags, uint8 tag) internal pure returns (uint cur) {
97
+ cur = create(base(source), source.length, 0, flags, tag);
98
+ }
99
+
100
+ // Inspection
101
+
102
+ /// @notice Decode the positional fields from the lower cursor.
103
+ /// @param cur Packed cursor or cursor pair.
104
+ /// @return i Current relative position.
105
+ /// @return offset Absolute base offset.
106
+ /// @return len Logical byte length.
107
+ function decode(uint cur) internal pure returns (uint i, uint offset, uint len) {
108
+ i = uint32(cur);
109
+ offset = uint32(cur >> 32);
110
+ len = uint32(cur >> 64);
111
+ }
112
+
113
+ /// @notice Return the active cursor without its position.
114
+ /// @dev Ignores the upper cursor when `cur` is a pair.
115
+ /// @param cur Packed cursor or cursor pair.
116
+ /// @return The lower cursor's offset, length, groups, flags, and tag.
117
+ function frame(uint cur) internal pure returns (uint) {
118
+ return clear32(uint128(cur), 0);
119
+ }
120
+
121
+ /// @notice Return the absolute position of the lower cursor as `offset + i`.
122
+ /// @dev Performs no bounds check.
123
+ /// @param cur Packed cursor or cursor pair.
124
+ /// @return Absolute current position.
125
+ function absolute(uint cur) internal pure returns (uint) {
126
+ return uint32(cur) + uint32(cur >> 32);
127
+ }
128
+
129
+ /// @notice Decode the consumer metadata and identity tag from the lower cursor.
130
+ /// @param cur Packed cursor or cursor pair.
131
+ /// @return groups Logical group count.
132
+ /// @return flags Consumer-defined flags.
133
+ /// @return tag Cursor identity tag.
134
+ function meta(uint cur) internal pure returns (uint groups, uint8 flags, uint8 tag) {
135
+ groups = uint16(cur >> 96);
136
+ flags = uint8(cur >> 112);
137
+ tag = uint8(cur >> 120);
138
+ }
139
+
140
+ /// @notice Return whether both packed cursors remain at their initial positions.
141
+ /// @param cur Packed cursor or cursor pair.
142
+ /// @return Whether both lane positions are zero.
143
+ function initial(uint cur) internal pure returns (bool) {
144
+ return uint32(cur) == 0 && uint32(cur >> 128) == 0;
145
+ }
146
+
147
+ /// @notice Return whether the lower cursor's current position precedes its length.
148
+ /// @param cur Packed cursor or cursor pair.
149
+ /// @return Whether the lower cursor has bytes remaining.
150
+ function more(uint cur) internal pure returns (bool) {
151
+ return uint32(cur) < uint32(cur >> 64);
152
+ }
153
+
154
+ /// @notice Return whether either packed cursor has remaining bytes.
155
+ /// @param cur Packed cursor or cursor pair.
156
+ /// @return Whether either lane has bytes remaining.
157
+ function any(uint cur) internal pure returns (bool) {
158
+ return more(cur) || more(cur >> 128);
159
+ }
160
+
161
+ /// @notice Require the lower cursor to be positioned at `i`.
162
+ /// @param cur Packed cursor or cursor pair.
163
+ /// @param i Expected relative position.
164
+ function expect(uint cur, uint i) internal pure {
165
+ if (uint32(cur) != i) revert UnexpectedPosition();
166
+ }
167
+
168
+ /// @notice Require the lower cursor to be positioned at absolute position `abs`.
169
+ /// @param cur Packed cursor or cursor pair.
170
+ /// @param abs Expected absolute position.
171
+ function expectAbs(uint cur, uint abs) internal pure {
172
+ if (absolute(cur) != abs) revert UnexpectedPosition();
173
+ }
174
+
175
+ /// @notice Return whether the lower cursor contains `flag`.
176
+ /// @param cur Packed cursor or cursor pair.
177
+ /// @param flag Consumer-defined flag bit or bit set.
178
+ /// @return Whether any requested flag bit is present.
179
+ function flagged(uint cur, uint8 flag) internal pure returns (bool) {
180
+ return uint8(cur >> 112) & flag != 0;
181
+ }
182
+
183
+ // Navigation
184
+
185
+ /// @dev Return `cur` after ensuring its lower position does not exceed its length.
186
+ /// @param cur Packed cursor or cursor pair.
187
+ /// @return Validated cursor unchanged.
188
+ function validate(uint cur) private pure returns (uint) {
189
+ if (uint32(cur) > uint32(cur >> 64)) revert OutOfBounds();
190
+ return cur;
191
+ }
192
+
193
+ /// @notice Move the lower cursor forward to `i`.
194
+ /// @param cur Packed cursor or cursor pair.
195
+ /// @param i New relative position; must not move backward.
196
+ /// @return updated Cursor with the new lower position.
197
+ function seek(uint cur, uint i) internal pure returns (uint updated) {
198
+ uint current = uint32(cur);
199
+ uint len = uint32(cur >> 64);
200
+ if (i < current || i > len) revert OutOfBounds();
201
+ updated = clear32(cur, 0) | i;
202
+ }
203
+
204
+ /// @notice Replace the lower cursor's position using an absolute position.
205
+ /// @param cur Packed cursor or cursor pair.
206
+ /// @param abs New absolute position.
207
+ /// @return updated Cursor with the corresponding relative position.
208
+ function seekAbs(uint cur, uint abs) internal pure returns (uint updated) {
209
+ uint offset = uint32(cur >> 32);
210
+ if (abs < offset) revert OutOfBounds();
211
+ updated = seek(cur, abs - offset);
212
+ }
213
+
214
+ /// @notice Advance the current position of the lower cursor.
215
+ /// @param cur Packed cursor or cursor pair.
216
+ /// @param amount Number of bytes to advance.
217
+ /// @return updated Cursor advanced by `amount`.
218
+ function advance(uint cur, uint amount) internal pure returns (uint updated) {
219
+ uint i = uint32(validate(cur));
220
+ uint len = uint32(cur >> 64);
221
+ if (amount > len - i) revert OutOfBounds();
222
+ updated = cur + amount;
223
+ }
224
+
225
+ /// @notice Replace the logical length of the lower cursor.
226
+ /// @dev A cursor cannot be resized below its current position.
227
+ /// @param cur Packed cursor or cursor pair.
228
+ /// @param len New logical length.
229
+ /// @return updated Cursor with the replaced length.
230
+ function resize(uint cur, uint len) internal pure returns (uint updated) {
231
+ if (uint32(cur) > max32(len)) revert OutOfBounds();
232
+ updated = clear32(cur, 64) | (len << 64);
233
+ }
234
+
235
+ /// @notice Create a child cursor over `[start, end)` within the lower cursor.
236
+ /// @dev The child starts at position zero, has no groups, and uses the
237
+ /// supplied tag. Any higher cursor is omitted.
238
+ /// @param cur Parent cursor or cursor pair.
239
+ /// @param start Child start relative to the parent base.
240
+ /// @param end Child exclusive end relative to the parent base.
241
+ /// @param tag Child identity tag.
242
+ /// @return child Packed child cursor.
243
+ function slice(uint cur, uint start, uint end, uint8 tag) internal pure returns (uint child) {
244
+ (, uint offset, uint len) = decode(validate(cur));
245
+ if (start > end || end > len) revert OutOfBounds();
246
+ child = create(offset + start, end - start, 0, 0, tag);
247
+ }
248
+
249
+ // Pairing and selection
250
+
251
+ /// @notice Reconcile both packed cursor lanes with an expected group count.
252
+ /// @dev Zero lanes and lanes with zero groups do not constrain the result.
253
+ /// @param cur Packed cursor or cursor pair.
254
+ /// @param expected Expected group count; zero accepts the encoded count.
255
+ /// @return groups Reconciled effective group count.
256
+ function reconcile(uint cur, uint expected) internal pure returns (uint groups) {
257
+ uint low = uint16(cur >> 96);
258
+ uint high = uint16(cur >> 224);
259
+ if (low != 0 && high != 0 && low != high) revert BadRatio();
260
+
261
+ groups = low != 0 ? low : high;
262
+ if (groups != 0 && expected != 0 && groups != expected) revert BadRatio();
263
+ if (groups == 0) groups = expected;
264
+ max16(groups);
265
+ }
266
+
267
+ /// @notice Combine two cursors into one packed word.
268
+ /// @dev Zero represents absence and acts as the identity value.
269
+ /// @param low Cursor placed in the lower lane.
270
+ /// @param high Cursor placed in the higher lane.
271
+ /// @return cur Packed cursor pair, or the nonzero cursor when one is absent.
272
+ function pair(uint low, uint high) internal pure returns (uint cur) {
273
+ low = validate(max128(low));
274
+ high = validate(max128(high));
275
+
276
+ if (low == 0) return high;
277
+ if (high == 0) return low;
278
+
279
+ uint8 tag = uint8(low >> 120);
280
+ if (tag == uint8(high >> 120)) revert DuplicateTag(tag);
281
+
282
+ cur = low | (high << 128);
283
+ }
284
+
285
+ /// @notice Swap the lower and higher cursors.
286
+ /// @param cur Packed cursor pair.
287
+ /// @return updated Pair with its lanes exchanged.
288
+ function swap(uint cur) internal pure returns (uint updated) {
289
+ updated = (cur << 128) | (cur >> 128);
290
+ }
291
+
292
+ /// @notice Return whether either packed cursor has the nonzero `expected` tag.
293
+ /// @dev Tag zero denotes an untagged cursor and is never matched by this helper.
294
+ /// @param cur Packed cursor or cursor pair.
295
+ /// @param expected Nonzero identity tag to find.
296
+ /// @return Whether either lane has the requested tag.
297
+ function contains(uint cur, uint8 expected) internal pure returns (bool) {
298
+ return expected != 0 && (uint8(cur >> 120) == expected || uint8(cur >> 248) == expected);
299
+ }
300
+
301
+ /// @notice Move the cursor with `expected` into the lower position.
302
+ /// @dev If both cursors share the tag, the existing lower cursor is retained.
303
+ /// @param cur Packed cursor or cursor pair.
304
+ /// @param expected Identity tag to select.
305
+ /// @return updated Cursor word with the selected lane in the lower position.
306
+ function select(uint cur, uint8 expected) internal pure returns (uint updated) {
307
+ if (uint8(cur >> 120) == expected) return cur;
308
+
309
+ updated = swap(cur);
310
+ if (uint8(updated >> 120) != expected) revert MissingCursor();
311
+ }
312
+
313
+ // Marks
314
+
315
+ /// @notice Select the live cursor whose frame matches the active cursor in `mark`.
316
+ /// @dev Only the lower 128 bits of `mark` are considered. The returned value
317
+ /// preserves the cursor pair and places the matching cursor in the lower half.
318
+ /// A zero frame may select an empty cursor lane.
319
+ /// @param cur Packed cursor or cursor pair to search.
320
+ /// @param mark Cursor-shaped positional reference to match.
321
+ /// @return located Cursor pair with the matching cursor active.
322
+ function locate(uint cur, uint mark) internal pure returns (uint located) {
323
+ uint expected = frame(mark);
324
+ if (frame(cur) == expected) return cur;
325
+
326
+ located = swap(cur);
327
+ if (frame(located) != expected) revert MissingCursor();
328
+ }
329
+
330
+ /// @notice Return whether a matched cursor is positioned before `mark`.
331
+ /// @dev Returns false at the mark and reverts after it. Only the active lower
332
+ /// cursor in `mark` participates in the comparison. A zero mark represents
333
+ /// the already-reached position of an empty cursor.
334
+ /// @param cur Packed cursor or cursor pair containing the marked cursor.
335
+ /// @param mark Cursor-shaped positional reference.
336
+ /// @return Whether the live cursor position precedes the marked position.
337
+ function before(uint cur, uint mark) internal pure returns (bool) {
338
+ uint i = uint32(locate(cur, mark));
339
+ uint target = uint32(mark);
340
+ if (i > target) revert OutOfBounds();
341
+ return i < target;
342
+ }
343
+
344
+ // Consumption
345
+
346
+ /// @notice Return a cursor's absolute position and advance it.
347
+ /// @dev Intended for fixed-width consumers that know their complete encoded size.
348
+ /// @param cur Packed cursor or cursor pair.
349
+ /// @param amount Number of bytes to consume.
350
+ /// @return updated Cursor advanced by `amount`.
351
+ /// @return abs Absolute pre-advance position.
352
+ function consume(uint cur, uint amount) internal pure returns (uint updated, uint abs) {
353
+ abs = Cursors.absolute(cur);
354
+ updated = advance(cur, amount);
355
+ }
356
+
357
+ /// @notice Select a tagged cursor, return its absolute position, and advance it.
358
+ /// @param cur Packed cursor or cursor pair.
359
+ /// @param tag Identity tag to select.
360
+ /// @param amount Number of bytes to consume.
361
+ /// @return updated Cursor pair with the selected lane advanced.
362
+ /// @return abs Absolute pre-advance position in the selected lane.
363
+ function consume(uint cur, uint8 tag, uint amount) internal pure returns (uint updated, uint abs) {
364
+ updated = select(cur, tag);
365
+ (updated, abs) = consume(updated, amount);
366
+ }
367
+ }
@@ -0,0 +1,14 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ /// @notice Conventional identifiers shared by endpoint execution lanes.
5
+ library Lanes {
6
+ /// @dev Decoder lane containing endpoint input blocks.
7
+ uint8 internal constant Input = 1;
8
+ /// @dev Decoder lane containing command state blocks.
9
+ uint8 internal constant State = 2;
10
+ /// @dev Writer lane containing regular endpoint output blocks.
11
+ uint8 internal constant Output = 3;
12
+ /// @dev Writer lane containing deferred transaction blocks.
13
+ uint8 internal constant Transactions = 4;
14
+ }
package/utils/Layout.sol CHANGED
@@ -40,8 +40,6 @@ library Layout {
40
40
 
41
41
  /// @dev Admin account — chain-local, backed by an EVM address.
42
42
  uint8 constant Admin = 0x01;
43
- /// @dev Guardian account — chain-local, backed by an EVM address.
44
- uint8 constant Guardian = 0x02;
45
43
  /// @dev User account — chain-agnostic, backed by an EVM address.
46
44
  uint8 constant User = 0x03;
47
45
  // -------------------------------------------------------------------------
@@ -6,9 +6,9 @@ pragma solidity ^0.8.33;
6
6
  library Selectors {
7
7
  /// @notice Derive the ABI selector for a command entrypoint.
8
8
  /// @param name Command function name.
9
- /// @return Selector for `name((bytes32,bytes,bytes))`.
9
+ /// @return Selector for `name(bytes32,bytes,bytes)`.
10
10
  function command(string memory name) internal pure returns (bytes4) {
11
- return derive(name, "((bytes32,bytes,bytes))");
11
+ return derive(name, "(bytes32,bytes,bytes)");
12
12
  }
13
13
 
14
14
  /// @notice Derive the ABI selector for a port entrypoint.