@rootzero/contracts 0.9.1 → 0.9.3

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 (57) hide show
  1. package/Commands.sol +16 -16
  2. package/Cursors.sol +1 -1
  3. package/Events.sol +3 -3
  4. package/Utils.sol +1 -1
  5. package/blocks/Cursors.sol +51 -9
  6. package/blocks/Keys.sol +1 -1
  7. package/blocks/Schema.sol +98 -4
  8. package/blocks/Writers.sol +21 -125
  9. package/commands/Base.sol +0 -19
  10. package/commands/Burn.sol +2 -2
  11. package/commands/Credit.sol +3 -2
  12. package/commands/Debit.sol +3 -3
  13. package/commands/Deposit.sol +6 -6
  14. package/commands/Pipe.sol +17 -12
  15. package/commands/Provision.sol +8 -8
  16. package/commands/Transfer.sol +2 -2
  17. package/commands/Withdraw.sol +3 -2
  18. package/commands/{control → admin}/AllowAssets.sol +5 -5
  19. package/commands/{control → admin}/Allowance.sol +9 -9
  20. package/commands/{control → admin}/Authorize.sol +5 -5
  21. package/commands/{control → admin}/DenyAssets.sol +5 -5
  22. package/commands/{control → admin}/Destroy.sol +4 -4
  23. package/commands/{control → admin}/Execute.sol +5 -5
  24. package/commands/{control → admin}/Init.sol +4 -4
  25. package/commands/{control → admin}/Unauthorize.sol +5 -5
  26. package/core/Access.sol +2 -2
  27. package/core/Calls.sol +16 -2
  28. package/core/Context.sol +10 -11
  29. package/core/Host.sol +4 -4
  30. package/core/Types.sol +1 -1
  31. package/events/{Control.sol → Admin.sol} +9 -5
  32. package/events/Command.sol +7 -2
  33. package/events/Listing.sol +3 -4
  34. package/events/Peer.sol +26 -0
  35. package/events/{RootZero.sol → Piped.sol} +3 -3
  36. package/events/Query.sol +5 -2
  37. package/package.json +1 -1
  38. package/peer/AllowAssets.sol +38 -0
  39. package/peer/Allowance.sol +35 -0
  40. package/peer/BalancePull.sol +43 -0
  41. package/peer/Base.sol +40 -0
  42. package/peer/DenyAssets.sol +38 -0
  43. package/peer/Settle.sol +32 -0
  44. package/queries/Assets.sol +4 -5
  45. package/queries/Balances.sol +4 -5
  46. package/queries/Positions.sol +4 -5
  47. package/utils/Accounts.sol +8 -0
  48. package/utils/Ids.sol +35 -30
  49. package/utils/Layout.sol +3 -3
  50. package/utils/Utils.sol +2 -2
  51. package/events/Remote.sol +0 -24
  52. package/remote/AllowAssets.sol +0 -39
  53. package/remote/Allowance.sol +0 -36
  54. package/remote/AssetPull.sol +0 -44
  55. package/remote/Base.sol +0 -40
  56. package/remote/DenyAssets.sol +0 -39
  57. package/remote/Settle.sol +0 -33
package/Commands.sol CHANGED
@@ -1,10 +1,10 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- // Aggregator: re-exports command, control, and remote abstractions.
4
+ // Aggregator: re-exports command, admin, and peer abstractions.
5
5
  // Import this file to inherit from the full rootzero command surface without managing individual paths.
6
6
 
7
- import { CommandBase, CommandContext, CommandPayable, encodeCommandCall } from "./commands/Base.sol";
7
+ import { CommandBase, CommandContext, CommandPayable } from "./commands/Base.sol";
8
8
  import { Keys } from "./blocks/Keys.sol";
9
9
  import { Burn, BurnHook } from "./commands/Burn.sol";
10
10
  import { CreditAccount, CreditAccountHook } from "./commands/Credit.sol";
@@ -14,20 +14,20 @@ import { PipePayable, PipePayableHook } from "./commands/Pipe.sol";
14
14
  import { Provision, ProvisionHook, ProvisionPayable, ProvisionPayableHook } from "./commands/Provision.sol";
15
15
  import { Transfer, TransferHook } from "./commands/Transfer.sol";
16
16
  import { Withdraw, WithdrawHook } from "./commands/Withdraw.sol";
17
- import { AllowAssets, AllowAssetsHook } from "./commands/control/AllowAssets.sol";
18
- import { Destroy, DestroyHook } from "./commands/control/Destroy.sol";
19
- import { ExecutePayable } from "./commands/control/Execute.sol";
20
- import { Authorize } from "./commands/control/Authorize.sol";
21
- import { DenyAssets, DenyAssetsHook } from "./commands/control/DenyAssets.sol";
22
- import { Init, InitHook } from "./commands/control/Init.sol";
23
- import { Allowance, AllowanceHook } from "./commands/control/Allowance.sol";
24
- import { Unauthorize } from "./commands/control/Unauthorize.sol";
25
- import { RemoteBase, encodeRemoteCall } from "./remote/Base.sol";
26
- import { RemoteAllowance } from "./remote/Allowance.sol";
27
- import { RemoteAssetPull, AssetPullHook } from "./remote/AssetPull.sol";
28
- import { RemoteAllowAssets } from "./remote/AllowAssets.sol";
29
- import { RemoteDenyAssets } from "./remote/DenyAssets.sol";
30
- import { RemoteSettle } from "./remote/Settle.sol";
17
+ import { AllowAssets, AllowAssetsHook } from "./commands/admin/AllowAssets.sol";
18
+ import { Destroy, DestroyHook } from "./commands/admin/Destroy.sol";
19
+ import { ExecutePayable } from "./commands/admin/Execute.sol";
20
+ import { Authorize } from "./commands/admin/Authorize.sol";
21
+ import { DenyAssets, DenyAssetsHook } from "./commands/admin/DenyAssets.sol";
22
+ import { Init, InitHook } from "./commands/admin/Init.sol";
23
+ import { Allowance, AllowanceHook } from "./commands/admin/Allowance.sol";
24
+ import { Unauthorize } from "./commands/admin/Unauthorize.sol";
25
+ import { PeerBase, encodePeerCall } from "./peer/Base.sol";
26
+ import { PeerAllowance } from "./peer/Allowance.sol";
27
+ import { PeerBalancePull, BalancePullHook } from "./peer/BalancePull.sol";
28
+ import { PeerAllowAssets } from "./peer/AllowAssets.sol";
29
+ import { PeerDenyAssets } from "./peer/DenyAssets.sol";
30
+ import { PeerSettle } from "./peer/Settle.sol";
31
31
 
32
32
 
33
33
 
package/Cursors.sol CHANGED
@@ -9,7 +9,7 @@ import { Forms, Sizes } from "./blocks/Schema.sol";
9
9
  import { Keys } from "./blocks/Keys.sol";
10
10
  import { Schemas } from "./blocks/Schema.sol";
11
11
  import { Cursors, Cur } from "./blocks/Cursors.sol";
12
- import { ALLOC_SCALE, Writer, Writers } from "./blocks/Writers.sol";
12
+ import { Writer, Writers } from "./blocks/Writers.sol";
13
13
 
14
14
 
15
15
 
package/Events.sol CHANGED
@@ -5,7 +5,7 @@ pragma solidity ^0.8.33;
5
5
  // Import this file to get access to every event emitter in one import.
6
6
 
7
7
  import { AccessEvent } from "./events/Access.sol";
8
- import { ControlEvent } from "./events/Control.sol";
8
+ import { AdminEvent } from "./events/Admin.sol";
9
9
  import { AssetEvent } from "./events/Asset.sol";
10
10
  import { BalanceEvent } from "./events/Balance.sol";
11
11
  import { CollateralEvent } from "./events/Collateral.sol";
@@ -17,9 +17,9 @@ import { EventEmitter } from "./events/Emitter.sol";
17
17
  import { GovernedEvent } from "./events/Governed.sol";
18
18
  import { HostAnnouncedEvent } from "./events/Host.sol";
19
19
  import { ListingEvent } from "./events/Listing.sol";
20
- import { RemoteEvent } from "./events/Remote.sol";
20
+ import { PeerEvent } from "./events/Peer.sol";
21
21
  import { QueryEvent } from "./events/Query.sol";
22
- import { RootZeroEvent } from "./events/RootZero.sol";
22
+ import { PipedEvent } from "./events/Piped.sol";
23
23
  import { SwapEvent } from "./events/Swap.sol";
24
24
  import { WithdrawalEvent } from "./events/Withdraw.sol";
25
25
 
package/Utils.sol CHANGED
@@ -11,7 +11,7 @@ import { ECDSA } from "./utils/ECDSA.sol";
11
11
  import { Ids, Selectors } from "./utils/Ids.sol";
12
12
  import { Layout } from "./utils/Layout.sol";
13
13
  import { Schemas } from "./blocks/Schema.sol";
14
- import { addrOr, applyBps, beforeBps, bytes32ToInt, bytes32ToString, divisible, hash32, intToBytes32, isFamily, isLocal, isLocalFamily, matchesBase, MAX_BPS, max8, max16, max24, max32, max40, max64, max96, max128, max160, NotDivisible, retryTicket, toLocalBase, toLocalFamily, toUnspecifiedBase, ValueOverflow } from "./utils/Utils.sol";
14
+ import { addrOr, applyBps, beforeBps, bytes32ToInt, bytes32ToString, divisible, hash32, intToBytes32, isFamily, isLocalChain, isLocalFamily, matchesBase, MAX_BPS, max8, max16, max24, max32, max40, max64, max96, max128, max160, NotDivisible, retryTicket, toLocalBase, toLocalFamily, toUnspecifiedBase, ValueOverflow } from "./utils/Utils.sol";
15
15
  import { Budget, Values } from "./utils/Value.sol";
16
16
 
17
17
 
@@ -4,7 +4,7 @@ pragma solidity ^0.8.33;
4
4
  import {AssetAmount, AccountAsset, AccountAmount, HostAmount, HostAccountAsset, Tx} from "../core/Types.sol";
5
5
  import {Sizes} from "./Schema.sol";
6
6
  import {Keys} from "./Keys.sol";
7
- import {ALLOC_SCALE, Writer, Writers} from "./Writers.sol";
7
+ import {Writer, Writers} from "./Writers.sol";
8
8
 
9
9
  /// @notice Zero-copy view into a calldata block stream.
10
10
  /// All positions (`i`, `bound`) are byte offsets relative to the start of the source region.
@@ -45,7 +45,7 @@ library Cursors {
45
45
  error ZeroNode();
46
46
  /// @dev A field value did not match the expected value.
47
47
  error UnexpectedValue();
48
- /// @dev Input and output block counts are not proportional to their declared group sizes.
48
+ /// @dev Prime block counts are not divisible by, or do not match, their declared group sizes.
49
49
  error BadRatio();
50
50
  // -------------------------------------------------------------------------
51
51
  // Cursor construction and navigation
@@ -87,6 +87,25 @@ library Cursors {
87
87
  out.len = to - from;
88
88
  }
89
89
 
90
+ /// @notice Return the full cursor region as a calldata slice.
91
+ /// Does not advance the cursor; `cur.i` and `cur.bound` are ignored.
92
+ /// @param cur Cursor whose backing region should be returned.
93
+ /// @return data Calldata view over `[cur.offset, cur.offset + cur.len)`.
94
+ function raw(Cur memory cur) internal pure returns (bytes calldata data) {
95
+ if (cur.len > msg.data.length || cur.offset > msg.data.length - cur.len) revert MalformedBlocks();
96
+ data = msg.data[cur.offset:cur.offset + cur.len];
97
+ }
98
+
99
+ /// @notice Return a sub-range of the cursor region as a calldata slice.
100
+ /// Does not advance the cursor; `cur.i` and `cur.bound` are ignored.
101
+ /// @param cur Source cursor.
102
+ /// @param from Start byte offset within the source region (inclusive).
103
+ /// @param to End byte offset within the source region (exclusive).
104
+ /// @return data Calldata view over the requested sub-range.
105
+ function raw(Cur memory cur, uint from, uint to) internal pure returns (bytes calldata data) {
106
+ data = cur.slice(from, to).raw();
107
+ }
108
+
90
109
  /// @notice Read a block header at position `i` without advancing the cursor.
91
110
  /// @param cur Source cursor.
92
111
  /// @param i Byte offset of the block header within the source region.
@@ -109,17 +128,30 @@ library Cursors {
109
128
  return cur.i + Sizes.Header + len;
110
129
  }
111
130
 
112
- /// @notice Return true if the current cursor position contains a block header with the given key.
131
+ /// @notice Return true if the current cursor position is at a block header with the given key.
113
132
  /// Returns false when `cur.i` is out of bounds or the key differs.
114
133
  /// @param cur Source cursor.
115
134
  /// @param key Expected block type identifier.
116
135
  /// @return Whether the block header at `cur.i` uses `key`.
117
- function has(Cur memory cur, bytes4 key) internal pure returns (bool) {
136
+ function isAt(Cur memory cur, bytes4 key) internal pure returns (bool) {
118
137
  if (cur.i + 8 > cur.len) return false;
119
138
  uint abs = cur.offset + cur.i;
120
139
  return bytes4(msg.data[abs:abs + 4]) == key;
121
140
  }
122
141
 
142
+ /// @notice Return whether the remaining cursor region is empty or exactly one block with `key`.
143
+ /// Returns false for an empty remaining region. Reverts if the next block has another key or
144
+ /// if a matching block is followed by trailing bytes.
145
+ /// @param cur Source cursor.
146
+ /// @param key Expected optional block key.
147
+ /// @return Whether the remaining region contains exactly one block with `key`.
148
+ function maybeOnly(Cur memory cur, bytes4 key) internal pure returns (bool) {
149
+ if (cur.i == cur.len) return false;
150
+ if (!cur.isAt(key)) revert InvalidBlock();
151
+ if (cur.past() != cur.len) revert IncompleteCursor();
152
+ return true;
153
+ }
154
+
123
155
  /// @notice Validate a block at position `i` and return its payload location.
124
156
  /// Does not advance the cursor.
125
157
  /// @param cur Source cursor.
@@ -249,23 +281,33 @@ library Cursors {
249
281
  cur.i = next;
250
282
  }
251
283
 
284
+ /// @notice Consume an optional block with the given key and return a cursor over the full block slice.
285
+ /// If the current block key does not match, returns an empty cursor and leaves `cur.i` unchanged.
286
+ /// Otherwise behaves like `take(cur, key)`.
287
+ /// @param cur Cursor positioned at an optional block.
288
+ /// @param key Optional block type key.
289
+ /// @return out Cursor scoped to the full matching block, or empty when no matching block is present.
290
+ function maybeTake(Cur memory cur, bytes4 key) internal pure returns (Cur memory out) {
291
+ return cur.isAt(key) ? take(cur, key) : cur.slice(cur.i, cur.i);
292
+ }
293
+
252
294
  /// @notice Consume an optional ROUTE block at the current position and return a cursor over the full block slice.
253
295
  /// If the current block is not ROUTE, returns an empty cursor and leaves `cur.i` unchanged.
254
296
  /// Otherwise behaves like `take(cur, Keys.Route)`.
255
297
  /// @param cur Cursor positioned at an optional ROUTE block.
256
298
  /// @return out Cursor scoped to the full ROUTE block, or empty when no ROUTE block is present.
257
299
  function maybeRoute(Cur memory cur) internal pure returns (Cur memory out) {
258
- return cur.has(Keys.Route) ? take(cur, Keys.Route) : cur.slice(cur.i, cur.i);
300
+ return maybeTake(cur, Keys.Route);
259
301
  }
260
302
 
261
- /// @notice Enter a List block, prime its member run, and return the raw block count.
303
+ /// @notice Enter a List block, prime its member run, and return the group count.
262
304
  /// @param cur Cursor positioned at a list block; advanced past the 8-byte header.
263
305
  /// @param group Expected block group size for the list item stream.
264
- /// @return count Total number of blocks in the list payload (a multiple of `group`).
306
+ /// @return groups Number of block groups in the list payload.
265
307
  /// @return next Byte offset immediately after the list payload.
266
- function list(Cur memory cur, uint group) internal pure returns (uint count, uint next) {
308
+ function list(Cur memory cur, uint group) internal pure returns (uint groups, uint next) {
267
309
  next = list(cur);
268
- (, count, ) = cur.primeRun(group);
310
+ (, , groups) = cur.primeRun(group);
269
311
  if (cur.bound != next) revert IncompleteCursor();
270
312
  }
271
313
 
package/blocks/Keys.sol CHANGED
@@ -34,7 +34,7 @@ library Keys {
34
34
  bytes4 constant Bundle = bytes4(keccak256("bundle(bytes data)"));
35
35
  /// @dev List wrapper - (bytes data); payload is an embedded repeated block stream
36
36
  bytes4 constant List = bytes4(keccak256("list(bytes data)"));
37
- /// @dev Frame wrapper - (bytes data); payload is schema-defined concatenated member payloads
37
+ /// @dev Frame wrapper - (bytes data); payload is schema-defined fields, optionally followed by block-stream items
38
38
  bytes4 constant Frame = bytes4(keccak256("frame(bytes data)"));
39
39
  /// @dev Extensible routing field - (bytes data); layout is command-defined
40
40
  bytes4 constant Route = bytes4(keccak256("route(bytes data)"));
package/blocks/Schema.sol CHANGED
@@ -16,24 +16,73 @@ pragma solidity ^0.8.33;
16
16
  // and can be decoded with `abi.decode`
17
17
  // - chain-specific payload blocks are request-only escape hatches and should never be used for pipeline state
18
18
  // - prefer ordinary protocol blocks whenever possible; chain-specific payload blocks should be a last resort
19
+ // - prefer frames for typed custom payloads; reserved extensible blocks such as `route(...)` and `item(...)`
20
+ // should be used only when a stable protocol-level key or opaque escape hatch is needed
19
21
  //
20
22
  // Schema DSL:
21
- // - `;` separates top-level sibling blocks
23
+ // - `;` separates top-level items
24
+ // - command, peer, and query schemas have the top-level form `prime`, `prime; global; global`,
25
+ // `empty; global; global`, or `""` for no request items at all
26
+ // - the first top-level item is the prime item unless it is the schema-only `empty` sentinel
27
+ // - the prime item must have one runtime key; helpers consume the consecutive run of blocks with
28
+ // that key as the per-operation batch, and `Command.shape` is defined over those prime runs
29
+ // - `empty` emits no block, has no runtime key, and must only appear as the first top-level item
30
+ // before global blocks; it means the schema has no prime item
31
+ // - top-level items after the prime item or `empty` are global batch support blocks: they apply to
32
+ // the whole batch, may be searched or consumed separately by the command, and are not counted as
33
+ // per-operation prime blocks
22
34
  // - `&` bundles adjacent blocks into one bundle block
23
35
  // - `+` frames adjacent fixed-layout block payloads into one frame block
24
- // - `&` and `+` follow the same grouping and normalization rules, except they emit different wrapper blocks
36
+ // - `+&` appends adjacent blocks to a frame as full block-stream items, preserving their headers
37
+ // - leading `&` is shorthand for an anonymous bundle
38
+ // - leading `+&` is shorthand for an anonymous frame with only a block-stream tail
39
+ // - `&`, `+`, and `+&` follow the same grouping and normalization rules, except they emit different payload forms
25
40
  // - `name = a & b` introduces a named bundle item with a local key derived from the raw input string
26
41
  // - `name = a + b` introduces a named frame item with a local key derived from the raw input string
42
+ // - `alias: item` attaches a presentation alias to the following schema item for off-chain APIs,
43
+ // docs, and UI labels
27
44
  // - `bundle = a & b` introduces an anonymous child bundle item
28
45
  // - `frame = a + b` introduces an anonymous child frame item, like `bundle = ...` and `list = ...`
46
+ // - `(fields...)` in item position is shorthand for an anonymous frame with those fields
47
+ // - `(fields...)` inside an existing frame introduces anonymous flattened frame fields without a nested frame key
29
48
  // - postfix `[]` marks a repeated list in the simple suffix form, e.g. `asset(...)[]`
49
+ // - postfix `?` marks an optional item, e.g. `account(...)?`
30
50
  // - `name[] = a & b` introduces a named list with a local key derived from the raw input string
51
+ // - `name? = a & b` introduces an optional named structural item
52
+ // - `name[]? = a & b` introduces an optional named list; `?[]` is invalid
31
53
  // - `list = a & b` introduces an anonymous list whose repeated item is the bundled shape `a & b`
54
+ // - `bundle? = a & b`, `frame? = a + b`, and `list? = a & b` introduce optional anonymous structural items
32
55
  // - empty entries are ignored, but structural markers are preserved after normalization
56
+ // - aliases are presentation metadata: they do not add bytes, headers, fields, or wrappers,
57
+ // and they do not change the payload layout of the item they label
58
+ // - aliases are not stripped before on-chain local-key derivation; when a named structural item is keyed by
59
+ // bytes4(keccak256(bytes(rawSchemaInput))), any alias text present in that raw string is part of identity
60
+ // - off-chain presentation should use `alias:` when present, otherwise the schema item name when available
61
+ // - if sibling presentation names collide, off-chain tools should append zero-based suffixes in encounter order,
62
+ // e.g. `amount(...) + amount(...)` presents as `amount0` and `amount1`
63
+ // - when possible, off-chain tools should preserve item grouping for repeated schemas, e.g. `amount0.asset`,
64
+ // `amount0.meta`, `amount0.amount`, then `amount1.asset`, `amount1.meta`, `amount1.amount`
65
+ // - if fields within one presentation group collide, off-chain tools should suffix those field names in
66
+ // encounter order, e.g. `(uint amount, uint amount)` presents as `amount0` and `amount1`
67
+ // - optionality is schema metadata only; when an optional item is present, it uses the same encoding as the
68
+ // non-optional form, and when absent, no placeholder block is emitted
69
+ // - optional children inside a present bundle, list item, or `+&` frame tail may be absent while the
70
+ // parent structural block is still emitted; for example `& account(...)?` and `+& account(...)?`
71
+ // can encode as an empty BUNDLE or empty FRAME when the account child is absent
72
+ // - optional `?` is not allowed on flattened `+` frame members because frame fields have no child key or length;
73
+ // use `(fields...)?` as an optional frame item, make the whole frame optional, or use `+&` for
74
+ // optional block-stream tail items
33
75
  // - grouping parentheses are not part of the DSL; parentheses are only used in block field lists
76
+ // and anonymous frame field groups
77
+ // - anonymous frame field groups in item position are promoted to anonymous frame items, so `(fields...)[]`
78
+ // means a list of anonymous frame items and `(fields...)?` means an optional anonymous frame item
79
+ // - anonymous frame field groups inside an existing frame are flattened fields, not nested frame items, and
80
+ // cannot be appended with `+&`
34
81
  // - `+` binds tighter than `&`, so `a & b + c` normalizes as `a & (b + c)`
82
+ // - `+&` binds like `+`, so `a & b +& c` normalizes as `a & (b +& c)`
35
83
  // - if `&` appears, the result remains a bundle even when only one non-empty child remains
36
84
  // - if `+` appears, the result remains a frame even when only one non-empty child remains
85
+ // - if `+&` appears, the result remains a frame even when only one non-empty child remains
37
86
  // - after ignoring empty entries, repeated adjacent separators collapse while preserving bundle/frame/list shape
38
87
  // - bundled blocks preserve member order, so `a & b` differs from `b & a`
39
88
  // - a bundle block's self payload is an embedded normal block stream of its bundled members
@@ -42,14 +91,20 @@ pragma solidity ^0.8.33;
42
91
  // - a frame block's self payload is the concatenated payload fields of its framed members
43
92
  // - framed members do not keep their ordinary block headers; the emitted schema defines the frame layout
44
93
  // - framed members must be fixed-layout block forms because frame payloads contain no child lengths or block keys
94
+ // - anonymous frame field groups must also be fixed-layout and contribute only their fields, in order
95
+ // - `+&` members keep their ordinary block encoding inside the frame payload, so dynamic blocks are allowed there
96
+ // - `+&` starts a block-stream tail inside the frame; fixed-layout `+` members may not follow it
45
97
  // - frames may be bundled like ordinary block items, but bundles/lists cannot be framed
46
- // - top-level blocks of the same type should be grouped together
47
- // - primary / driving blocks should appear before auxiliary blocks
98
+ // - prime blocks of the same type should be grouped together so the prime run is contiguous
99
+ // - primary / driving blocks should be the prime item; auxiliary batch-wide blocks should follow
100
+ // as global `;` siblings
48
101
  // - `route(<fields...>)`, `item(<fields...>)`, `evm(<fields...>)`, `query(<fields...>)`,
49
102
  // and `response(<fields...>)` are reserved extensible schema forms whose keys are always
50
103
  // `Keys.Route`, `Keys.Item`, `Keys.Evm`, `Keys.Query`, and `Keys.Response` respectively
51
104
  // - these extensible forms work like dynamic `bytes` blocks: they may carry arbitrary
52
105
  // payload bytes while keeping one fixed key per semantic block type
106
+ // - prefer `frame = ...`, `(fields...)`, and `+&` tails over `route(...)` and `item(...)` when the payload
107
+ // can be described by the schema DSL; frames give off-chain tools typed layouts and better generated APIs
53
108
  // - `evm(<fields...>)` differs from bundle/list payloads: its bytes are not an embedded block stream
54
109
  // - `evm(uint foo, uint bar)` is a schema declaration only; on-chain the block key is still `Keys.Evm`
55
110
  // and the payload can be decoded from `bytes data` using the local runtime's native decoder
@@ -57,21 +112,59 @@ pragma solidity ^0.8.33;
57
112
  // - anonymous `&` compiles to a `Keys.Bundle` block whose self payload is the bundled member block stream
58
113
  // - anonymous `[]` compiles to a `Keys.List` block whose self payload is the repeated item block stream
59
114
  // - anonymous `+` compiles to a `Keys.Frame` block whose self payload is the framed member payload fields
115
+ // - anonymous `+&` compiles to a `Keys.Frame` block whose self payload is fixed framed fields
116
+ // followed by an embedded normal block-stream tail
117
+ // - bare `(fields...)` compiles to a `Keys.Frame` block whose self payload is those fields
60
118
  // - named lists, bundles, and frames use bytes4(keccak256(bytes(rawSchemaInput))) as their block key
61
119
  // - named structural keys are custom/local identifiers, not global protocol keys; formatting is part of identity
120
+ // - `payment: frame = amount(...) +& fee(...)` means an anonymous frame whose runtime key is still `Keys.Frame`,
121
+ // with `payment` as its off-chain presentation name
122
+ // - `quote: payment = amount(...) + fee(...)` means a named frame with presentation alias `quote`;
123
+ // its local runtime key is derived from the full raw schema string including `quote:`
124
+ // - `frame = debit: amount(...) + credit: amount(...)` means the two flattened amount groups should be
125
+ // presented off-chain as `debit` and `credit`, while encoding remains the same as without aliases
126
+ // - `frame = amount(...) + amount(...)` has the same encoding as the aliased form above, but off-chain tools
127
+ // should present the groups as `amount0` and `amount1`
62
128
  // - `asset(...)[]` means a list whose repeated item is the block `asset(...)`
129
+ // - `account(...)?` means an optional account block
63
130
  // - `steps[] = asset(...) & account(...)` means a named list whose repeated item is the bundle
64
131
  // `asset(...) & account(...)`
132
+ // - `steps[]? = asset(...) & account(...)` means that named list may be absent
133
+ // - `memo? = evm(bytes data)` means an optional named local item whose payload, when present, is EVM-encoded
65
134
  // - `payment = amount(...) + fee(...)` means a named frame whose payload is
66
135
  // `asset | meta | amount | fee` and whose on-chain key is derived from that raw schema string
136
+ // - `payment = amount(...) + (uint fee, uint rate)` means a named frame whose payload is
137
+ // `asset | meta | amount | fee | rate`, without inventing a child block name for the extra fields
138
+ // - `payment = (uint fee, uint rate)` means a named frame whose payload is `fee | rate`
139
+ // - `payment = amount(...) +& fee(...)` means a named frame whose payload is
140
+ // `asset | meta | amount | [fee key | fee len | fee amount]`
141
+ // - `payment = amount(...) +& fee(...)?` means a named frame with an amount prefix and optional fee block tail
142
+ // - `payment = amount(...) +& list? = fee(...)` means a named frame with an amount prefix and
143
+ // an optional anonymous list block tail
144
+ // - `payment = amount(...)? + fee(...)` is invalid because `amount(...)` would be an optional flattened frame member
145
+ // - `(uint fee)` means an anonymous frame with one `fee` field
146
+ // - `(uint fee)[]` means a list of anonymous frames whose repeated item has one `fee` field
147
+ // - `(uint fee)?` means an optional anonymous frame with one `fee` field
148
+ // - `amount(...) & (uint fee)` means a bundle containing an amount block and an anonymous fee frame
149
+ // - `amount(...) +& (uint fee)` is invalid because `+&` appends existing block-stream items, not field groups
67
150
  // - `amount(...) & fee(...) + account(...)` means `amount(...) & (fee(...) + account(...))`;
68
151
  // it compiles to a bundle containing one amount block followed by one frame block
152
+ // - `amount(...) & fee(...) +& account(...)` means `amount(...) & (fee(...) +& account(...))`;
153
+ // it compiles to a bundle containing one amount block followed by one frame block with an account block tail
69
154
  // - `bundle = account(...) & evm(bytes routeData)` means an anonymous child bundle with those bundled members
155
+ // - `bundle? = account(...) & auth(...)` means that anonymous bundle may be absent
70
156
  // - `frame = amount(...) + fee(...)` means an anonymous child frame with those framed payload fields
157
+ // - `frame? = amount(...) + fee(...)` means that anonymous frame may be absent
158
+ // - `frame = amount(...) +& fee(...)` means an anonymous child frame with amount fields plus a fee block tail
71
159
  // - `list = asset(...) & account(...)` means an anonymous child list whose repeated item is the
72
160
  // bundle `asset(...) & account(...)`
161
+ // - `list? = fee(...)` means that anonymous list may be absent
73
162
  // - `"amount(...) &"` and `"& amount(...)"` both normalize to a bundle containing one `amount(...)` child
163
+ // - `"& account(...)?"` normalizes to a bundle containing one optional `account(...)` child
74
164
  // - `"amount(...) +"` and `"+ amount(...)"` both normalize to a frame containing one `amount(...)` payload
165
+ // - `"amount(...) +&"` and `"+& amount(...)"` both normalize to a frame containing one `amount(...)` block
166
+ // - `"+& account(...)?"` normalizes to a frame containing an optional `account(...)` block-stream tail
167
+ // - `"amount(...)?"` normalizes to an optional amount block; `"amount(...)?[]"` is invalid
75
168
  // - canonical blocks are `amount(...)` for request amounts, `balance(...)` for state balances,
76
169
  // `allocation(...)` for host-scoped provision requests, `allowance(...)` for host-scoped caps,
77
170
  // `custody(...)` for host-scoped state,
@@ -102,6 +195,7 @@ pragma solidity ^0.8.33;
102
195
  /// These strings are the canonical source from which `Keys` constants are derived
103
196
  /// and are used when emitting schema descriptors in command events.
104
197
  library Schemas {
198
+ string constant Empty = "empty";
105
199
  string constant Node = "node(uint id)";
106
200
  string constant Account = "account(bytes32 account)";
107
201
  string constant Asset = "asset(bytes32 asset, bytes32 meta)";
@@ -17,11 +17,6 @@ struct Writer {
17
17
  bytes dst;
18
18
  }
19
19
 
20
- // Fixed-point scaling denominator for output-count allocation.
21
- // A `scaledRatio` of `ALLOC_SCALE` means 1:1 (one output block per input block).
22
- // `2 * ALLOC_SCALE` means 2:1; non-integer ratios revert with `BadWriterRatio`.
23
- uint constant ALLOC_SCALE = 10_000;
24
-
25
20
  /// @title Writers
26
21
  /// @notice Response block stream builder for the rootzero protocol.
27
22
  /// Allocates a fixed-size memory buffer up front and writes binary-encoded
@@ -36,8 +31,6 @@ library Writers {
36
31
  error IncompleteWriter();
37
32
  /// @dev An alloc function received a zero count, or `finish` found no bytes written.
38
33
  error EmptyRequest();
39
- /// @dev `scaledRatio * count` is not evenly divisible by `ALLOC_SCALE`.
40
- error BadWriterRatio();
41
34
  /// @dev A fixed-width low-level writer received an invalid final-word keep length.
42
35
  error InvalidKeep();
43
36
 
@@ -58,21 +51,13 @@ library Writers {
58
51
  }
59
52
 
60
53
  /// @notice Core allocation routine used by all counted `alloc*` helpers.
61
- /// Computes `(count * scaledRatio / ALLOC_SCALE) * blockLen` and allocates that many bytes.
62
- /// @param count Number of input blocks.
63
- /// @param scaledRatio Output-to-input ratio in `ALLOC_SCALE` units.
54
+ /// Computes `count * blockLen` and allocates that many bytes.
55
+ /// @param count Number of output blocks.
64
56
  /// @param blockLen Logical byte size of each output block (including 8-byte header).
65
57
  /// @return writer Allocated writer.
66
- function allocFromScaledCount(
67
- uint count,
68
- uint scaledRatio,
69
- uint blockLen
70
- ) internal pure returns (Writer memory writer) {
58
+ function allocFromCount(uint count, uint blockLen) internal pure returns (Writer memory writer) {
71
59
  if (count == 0) revert EmptyRequest();
72
- uint scaledCount = count * scaledRatio;
73
- if (scaledCount % ALLOC_SCALE != 0) revert BadWriterRatio();
74
- uint len = (scaledCount / ALLOC_SCALE) * blockLen;
75
- writer = alloc(len);
60
+ writer = alloc(count * blockLen);
76
61
  }
77
62
 
78
63
  /// @notice Allocate a writer sized for exactly `count` dynamic blocks with a shared payload length.
@@ -81,96 +66,42 @@ library Writers {
81
66
  /// @param payloadLen Payload byte length for each block.
82
67
  /// @return writer Allocated writer.
83
68
  function allocBytes(uint count, uint payloadLen) internal pure returns (Writer memory writer) {
84
- return allocFromScaledCount(count, ALLOC_SCALE, Sizes.Header + payloadLen);
69
+ return allocFromCount(count, Sizes.Header + payloadLen);
85
70
  }
86
71
 
87
- /// @notice Allocate a writer sized for exactly `count` 32-byte-payload blocks (1:1 ratio).
72
+ /// @notice Allocate a writer sized for exactly `count` 32-byte-payload blocks.
88
73
  /// @param count Number of blocks to allocate space for.
89
74
  /// @return writer Allocated writer.
90
75
  function alloc32s(uint count) internal pure returns (Writer memory writer) {
91
- return allocFromScaledCount(count, ALLOC_SCALE, Sizes.B32);
76
+ return allocFromCount(count, Sizes.B32);
92
77
  }
93
78
 
94
- /// @notice Allocate a writer sized for exactly `count` 64-byte-payload blocks (1:1 ratio).
79
+ /// @notice Allocate a writer sized for exactly `count` 64-byte-payload blocks.
95
80
  /// @param count Number of blocks to allocate space for.
96
81
  /// @return writer Allocated writer.
97
82
  function alloc64s(uint count) internal pure returns (Writer memory writer) {
98
- return allocFromScaledCount(count, ALLOC_SCALE, Sizes.B64);
83
+ return allocFromCount(count, Sizes.B64);
99
84
  }
100
85
 
101
- /// @notice Allocate a writer sized for exactly `count` 96-byte-payload blocks (1:1 ratio).
86
+ /// @notice Allocate a writer sized for exactly `count` 96-byte-payload blocks.
102
87
  /// @param count Number of blocks to allocate space for.
103
88
  /// @return writer Allocated writer.
104
89
  function alloc96s(uint count) internal pure returns (Writer memory writer) {
105
- return allocFromScaledCount(count, ALLOC_SCALE, Sizes.B96);
90
+ return allocFromCount(count, Sizes.B96);
106
91
  }
107
92
 
108
- /// @notice Allocate a writer sized for exactly `count` 128-byte-payload blocks (1:1 ratio).
93
+ /// @notice Allocate a writer sized for exactly `count` 128-byte-payload blocks.
109
94
  /// @param count Number of blocks to allocate space for.
110
95
  /// @return writer Allocated writer.
111
96
  function alloc128s(uint count) internal pure returns (Writer memory writer) {
112
- return allocFromScaledCount(count, ALLOC_SCALE, Sizes.B128);
97
+ return allocFromCount(count, Sizes.B128);
113
98
  }
114
99
 
115
- /// @notice Allocate a writer sized for exactly `count` 160-byte-payload blocks (1:1 ratio).
100
+ /// @notice Allocate a writer sized for exactly `count` 160-byte-payload blocks.
116
101
  /// @param count Number of blocks to allocate space for.
117
102
  /// @return writer Allocated writer.
118
103
  function alloc160s(uint count) internal pure returns (Writer memory writer) {
119
- return allocFromScaledCount(count, ALLOC_SCALE, Sizes.B160);
120
- }
121
-
122
- /// @notice Allocate a writer for dynamic blocks with a shared payload length and custom output ratio.
123
- /// Each block reserves `Sizes.Header + payloadLen` bytes.
124
- /// @param count Number of input blocks.
125
- /// @param scaledRatio Output count multiplier expressed in `ALLOC_SCALE` units.
126
- /// @param payloadLen Payload byte length for each block.
127
- /// @return writer Allocated writer.
128
- function allocScaledBytes(
129
- uint count,
130
- uint scaledRatio,
131
- uint payloadLen
132
- ) internal pure returns (Writer memory writer) {
133
- return allocFromScaledCount(count, scaledRatio, Sizes.Header + payloadLen);
134
- }
135
-
136
- /// @notice Allocate a writer for 32-byte-payload blocks with a custom output-to-input ratio.
137
- /// @param count Number of input blocks.
138
- /// @param scaledRatio Output count multiplier expressed in `ALLOC_SCALE` units.
139
- /// @return writer Allocated writer.
140
- function allocScaled32s(uint count, uint scaledRatio) internal pure returns (Writer memory writer) {
141
- return allocFromScaledCount(count, scaledRatio, Sizes.B32);
142
- }
143
-
144
- /// @notice Allocate a writer for 64-byte-payload blocks with a custom output-to-input ratio.
145
- /// @param count Number of input blocks.
146
- /// @param scaledRatio Output count multiplier expressed in `ALLOC_SCALE` units.
147
- /// @return writer Allocated writer.
148
- function allocScaled64s(uint count, uint scaledRatio) internal pure returns (Writer memory writer) {
149
- return allocFromScaledCount(count, scaledRatio, Sizes.B64);
150
- }
151
-
152
- /// @notice Allocate a writer for 96-byte-payload blocks with a custom output-to-input ratio.
153
- /// @param count Number of input blocks.
154
- /// @param scaledRatio Output count multiplier expressed in `ALLOC_SCALE` units.
155
- /// @return writer Allocated writer.
156
- function allocScaled96s(uint count, uint scaledRatio) internal pure returns (Writer memory writer) {
157
- return allocFromScaledCount(count, scaledRatio, Sizes.B96);
158
- }
159
-
160
- /// @notice Allocate a writer for 128-byte-payload blocks with a custom output-to-input ratio.
161
- /// @param count Number of input blocks.
162
- /// @param scaledRatio Output count multiplier expressed in `ALLOC_SCALE` units.
163
- /// @return writer Allocated writer.
164
- function allocScaled128s(uint count, uint scaledRatio) internal pure returns (Writer memory writer) {
165
- return allocFromScaledCount(count, scaledRatio, Sizes.B128);
166
- }
167
-
168
- /// @notice Allocate a writer for 160-byte-payload blocks with a custom output-to-input ratio.
169
- /// @param count Number of input blocks.
170
- /// @param scaledRatio Output count multiplier expressed in `ALLOC_SCALE` units.
171
- /// @return writer Allocated writer.
172
- function allocScaled160s(uint count, uint scaledRatio) internal pure returns (Writer memory writer) {
173
- return allocFromScaledCount(count, scaledRatio, Sizes.B160);
104
+ return allocFromCount(count, Sizes.B160);
174
105
  }
175
106
 
176
107
  /// @notice Allocate a writer sized for exactly `count` STATUS form blocks.
@@ -180,83 +111,48 @@ library Writers {
180
111
  return alloc32s(count);
181
112
  }
182
113
 
183
- /// @notice Allocate a writer sized for exactly `count` ASSET blocks (1:1 ratio).
114
+ /// @notice Allocate a writer sized for exactly `count` ASSET blocks.
184
115
  /// @param count Number of asset blocks to allocate space for.
185
116
  /// @return writer Allocated writer.
186
117
  function allocAssets(uint count) internal pure returns (Writer memory writer) {
187
118
  return alloc64s(count);
188
119
  }
189
120
 
190
- /// @notice Allocate a writer sized for exactly `count` AMOUNT blocks (1:1 ratio).
121
+ /// @notice Allocate a writer sized for exactly `count` AMOUNT blocks.
191
122
  /// @param count Number of amount blocks to allocate space for.
192
123
  /// @return writer Allocated writer.
193
124
  function allocAmounts(uint count) internal pure returns (Writer memory writer) {
194
125
  return alloc96s(count);
195
126
  }
196
127
 
197
- /// @notice Allocate a writer sized for exactly `count` BALANCE blocks (1:1 ratio).
128
+ /// @notice Allocate a writer sized for exactly `count` BALANCE blocks.
198
129
  /// @param count Number of balance blocks to allocate space for.
199
130
  /// @return writer Allocated writer.
200
131
  function allocBalances(uint count) internal pure returns (Writer memory writer) {
201
132
  return alloc96s(count);
202
133
  }
203
134
 
204
- /// @notice Allocate a writer sized for exactly `count` ACCOUNT_AMOUNT form blocks (1:1 ratio).
135
+ /// @notice Allocate a writer sized for exactly `count` ACCOUNT_AMOUNT form blocks.
205
136
  /// @param count Number of account amount blocks to allocate space for.
206
137
  /// @return writer Allocated writer.
207
138
  function allocAccountAmounts(uint count) internal pure returns (Writer memory writer) {
208
139
  return alloc128s(count);
209
140
  }
210
141
 
211
- /// @notice Allocate a writer sized for exactly `count` CUSTODY blocks (1:1 ratio).
142
+ /// @notice Allocate a writer sized for exactly `count` CUSTODY blocks.
212
143
  /// @param count Number of custody blocks to allocate space for.
213
144
  /// @return writer Allocated writer.
214
145
  function allocCustodies(uint count) internal pure returns (Writer memory writer) {
215
146
  return alloc128s(count);
216
147
  }
217
148
 
218
- /// @notice Allocate a writer sized for exactly `count` TRANSACTION blocks (1:1 ratio).
149
+ /// @notice Allocate a writer sized for exactly `count` TRANSACTION blocks.
219
150
  /// @param count Number of transaction blocks to allocate space for.
220
151
  /// @return writer Allocated writer.
221
152
  function allocTransactions(uint count) internal pure returns (Writer memory writer) {
222
153
  return alloc160s(count);
223
154
  }
224
155
 
225
- /// @notice Allocate a writer for ASSET blocks with a custom output-to-input ratio.
226
- /// @param count Number of input blocks.
227
- /// @param scaledRatio Output count multiplier expressed in `ALLOC_SCALE` units
228
- /// (e.g. `ALLOC_SCALE` = 1:1, `2 * ALLOC_SCALE` = 2:1).
229
- /// @return writer Allocated writer.
230
- function allocScaledAssets(uint count, uint scaledRatio) internal pure returns (Writer memory writer) {
231
- return allocScaled64s(count, scaledRatio);
232
- }
233
-
234
- /// @notice Allocate a writer for AMOUNT blocks with a custom output-to-input ratio.
235
- /// @param count Number of input blocks.
236
- /// @param scaledRatio Output count multiplier expressed in `ALLOC_SCALE` units
237
- /// (e.g. `ALLOC_SCALE` = 1:1, `2 * ALLOC_SCALE` = 2:1).
238
- /// @return writer Allocated writer.
239
- function allocScaledAmounts(uint count, uint scaledRatio) internal pure returns (Writer memory writer) {
240
- return allocScaled96s(count, scaledRatio);
241
- }
242
-
243
- /// @notice Allocate a writer for BALANCE blocks with a custom output-to-input ratio.
244
- /// @param count Number of input blocks.
245
- /// @param scaledRatio Output count multiplier expressed in `ALLOC_SCALE` units
246
- /// (e.g. `ALLOC_SCALE` = 1:1, `2 * ALLOC_SCALE` = 2:1).
247
- /// @return writer Allocated writer.
248
- function allocScaledBalances(uint count, uint scaledRatio) internal pure returns (Writer memory writer) {
249
- return allocScaled96s(count, scaledRatio);
250
- }
251
-
252
- /// @notice Allocate a writer for CUSTODY blocks with a custom output-to-input ratio.
253
- /// @param count Number of input blocks.
254
- /// @param scaledRatio Output count multiplier in `ALLOC_SCALE` units.
255
- /// @return writer Allocated writer.
256
- function allocScaledCustodies(uint count, uint scaledRatio) internal pure returns (Writer memory writer) {
257
- return allocScaled128s(count, scaledRatio);
258
- }
259
-
260
156
  // -------------------------------------------------------------------------
261
157
  // Fixed-width write helpers
262
158
  // -------------------------------------------------------------------------