@rootzero/contracts 1.12.0 → 1.14.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 (76) hide show
  1. package/CHANGELOG.md +76 -8
  2. package/Codec.sol +21 -0
  3. package/Commands.sol +14 -0
  4. package/Core.sol +4 -4
  5. package/Endpoints.sol +5 -7
  6. package/README.md +62 -35
  7. package/Utils.sol +2 -4
  8. package/codec/Blocks.sol +1606 -0
  9. package/codec/Buffers.sol +165 -0
  10. package/codec/Decoders.sol +558 -0
  11. package/codec/Descriptors.sol +124 -0
  12. package/{blocks → codec}/Keys.sol +4 -28
  13. package/codec/Readers.sol +114 -0
  14. package/{blocks → codec}/Schema.sol +38 -80
  15. package/codec/Specs.sol +218 -0
  16. package/codec/Writers.sol +487 -0
  17. package/commands/Allocate.sol +50 -0
  18. package/commands/Base.sol +71 -37
  19. package/commands/Burn.sol +19 -13
  20. package/commands/Credit.sol +25 -31
  21. package/commands/Debit.sol +28 -38
  22. package/commands/Deposit.sol +39 -39
  23. package/commands/Payout.sol +22 -15
  24. package/commands/Provision.sol +40 -40
  25. package/commands/Recover.sol +21 -20
  26. package/commands/Relay.sol +25 -23
  27. package/commands/Withdraw.sol +18 -20
  28. package/commands/admin/AllowAssets.sol +20 -16
  29. package/commands/admin/Allowance.sol +19 -13
  30. package/commands/admin/Appoint.sol +18 -15
  31. package/commands/admin/Authorize.sol +24 -14
  32. package/commands/admin/Base.sol +1 -1
  33. package/commands/admin/DenyAssets.sol +20 -16
  34. package/commands/admin/Dismiss.sol +18 -15
  35. package/commands/admin/Execute.sol +20 -18
  36. package/commands/admin/Label.sol +18 -13
  37. package/commands/admin/Schemas.sol +19 -14
  38. package/commands/admin/Unauthorize.sol +24 -14
  39. package/core/Calls.sol +7 -14
  40. package/core/Endpoint.sol +43 -126
  41. package/core/Host.sol +10 -2
  42. package/core/Pipeline.sol +29 -25
  43. package/core/Settlement.sol +39 -0
  44. package/core/Types.sol +1 -1
  45. package/docs/Schema.md +38 -32
  46. package/events/Endpoint.sol +2 -2
  47. package/events/Introduction.sol +4 -3
  48. package/events/Schema.sol +5 -5
  49. package/execution/Budget.sol +40 -0
  50. package/execution/Execution.sol +1083 -0
  51. package/guards/Base.sol +8 -9
  52. package/guards/Revoke.sol +11 -9
  53. package/package.json +1 -1
  54. package/ports/AllowAssets.sol +12 -15
  55. package/ports/Allowance.sol +11 -9
  56. package/ports/Base.sol +18 -11
  57. package/ports/Credit.sol +11 -9
  58. package/ports/Debit.sol +11 -9
  59. package/ports/DenyAssets.sol +10 -13
  60. package/ports/Dispatch.sol +13 -15
  61. package/ports/Pipe.sol +14 -12
  62. package/ports/Redeem.sol +12 -9
  63. package/ports/Settle.sol +13 -13
  64. package/queries/Assets.sol +15 -15
  65. package/queries/Balances.sol +17 -17
  66. package/queries/Base.sol +11 -12
  67. package/utils/Actions.sol +1 -0
  68. package/utils/Cursors.sol +308 -0
  69. package/utils/Lanes.sol +14 -0
  70. package/utils/Selectors.sol +2 -2
  71. package/utils/Utils.sol +46 -0
  72. package/Cursors.sol +0 -16
  73. package/blocks/Cursors.sol +0 -1400
  74. package/blocks/Writers.sol +0 -1028
  75. package/core/Payable.sol +0 -53
  76. package/utils/Value.sol +0 -43
@@ -7,38 +7,20 @@ pragma solidity ^0.8.33;
7
7
  /// Custom block keys only need to be unique in the context where they are used;
8
8
  /// hosts may publish custom key meanings with the `Schema` event.
9
9
  library Keys {
10
- /// @notice Create a context-local block key.
11
- /// @dev Local keys are opaque tags for host- or endpoint-specific schemas.
12
- /// The caller is responsible for choosing values that are unique in the
13
- /// context where they are used and publishing their meaning with `Schema`.
14
- /// @param value Opaque local key value.
15
- /// @return Context-local block key.
16
- function local(uint32 value) internal pure returns (bytes4) {
17
- return bytes4(value);
18
- }
19
-
20
10
  /// @dev Empty / unset key.
21
11
  bytes4 constant Empty = bytes4(0);
22
12
  /// @dev Wildcard key used in discovery when any block stream is accepted.
23
13
  bytes4 constant Any = 0xffffffff;
24
- /// @dev Default context-local block key for hosts/endpoints that need one custom schema.
25
- bytes4 constant Local = bytes4(uint32(1));
26
14
  /// @dev Input amount - (bytes32 asset, uint amount)
27
15
  bytes4 constant Amount = bytes4(keccak256("#amount"));
28
16
  /// @dev Ledger balance - (bytes32 asset, uint amount)
29
17
  bytes4 constant Balance = bytes4(keccak256("#balance"));
30
- /// @dev Balance constraint - (bytes32 asset, uint min, uint max)
31
- bytes4 constant BalanceLimit = bytes4(keccak256("#balanceLimit"));
32
- /// @dev Host-scoped request amount - (uint host, bytes32 asset, uint amount)
18
+ /// @dev Host-scoped input amount - (uint host, bytes32 asset, uint amount)
33
19
  bytes4 constant Allocation = bytes4(keccak256("#allocation"));
34
20
  /// @dev Host-scoped allowance cap - (uint host, bytes32 asset, uint amount)
35
21
  bytes4 constant Allowance = bytes4(keccak256("#allowance"));
36
22
  /// @dev Cross-host custody state - (uint host, bytes32 asset, uint amount)
37
23
  bytes4 constant Custody = bytes4(keccak256("#custody"));
38
- /// @dev Cross-host custody constraint - (uint host, bytes32 asset, uint min, uint max)
39
- bytes4 constant CustodyLimit = bytes4(keccak256("#custodyLimit"));
40
- /// @dev Fee amount - (uint amount)
41
- bytes4 constant Fee = bytes4(keccak256("#fee"));
42
24
  /// @dev List wrapper; payload is an embedded repeated block stream
43
25
  bytes4 constant List = bytes4(keccak256("#list"));
44
26
  /// @dev EVM-encoded payload field; layout follows standard ABI tuple encoding
@@ -51,11 +33,11 @@ library Keys {
51
33
  bytes4 constant Account = bytes4(keccak256("#account"));
52
34
  /// @dev Transfer record passed through the pipeline - (bytes32 from, bytes32 to, bytes32 asset, uint amount)
53
35
  bytes4 constant Transaction = bytes4(keccak256("#transaction"));
54
- /// @dev Sub-command invocation - (uint target, uint resources, #bytes as request)
36
+ /// @dev Sub-command invocation - (uint cmd, uint resources, #bytes as input)
55
37
  bytes4 constant Step = bytes4(keccak256("#step"));
56
- /// @dev Portal relay request - (uint portal, uint resources, #bytes as request)
38
+ /// @dev Portal relay input - (uint portal, uint resources, #bytes as input)
57
39
  bytes4 constant Relay = bytes4(keccak256("#relay"));
58
- /// @dev Command context transport - (bytes32 account, #bytes as state, #bytes as request)
40
+ /// @dev Command context transport - (bytes32 account, #bytes as state, #bytes as input)
59
41
  bytes4 constant Context = bytes4(keccak256("#context"));
60
42
  /// @dev Recoverable witness - (uint handler, uint resources, bytes32 key, #bytes as witness)
61
43
  bytes4 constant Recover = bytes4(keccak256("#recover"));
@@ -63,14 +45,10 @@ library Keys {
63
45
  bytes4 constant Dispatch = bytes4(keccak256("#dispatch"));
64
46
  /// @dev Raw external call - (uint target, uint resources, #bytes as payload)
65
47
  bytes4 constant Call = bytes4(keccak256("#call"));
66
- /// @dev Authentication proof - (uint cid, uint deadline, #bytes as proof); must appear last in its segment
67
- bytes4 constant Auth = bytes4(keccak256("#auth"));
68
48
  /// @dev Asset descriptor without amount - (bytes32 asset)
69
49
  bytes4 constant Asset = bytes4(keccak256("#asset"));
70
50
  /// @dev Node identifier - (uint id)
71
51
  bytes4 constant Node = bytes4(keccak256("#node"));
72
- /// @dev Relayer bounty - (uint amount, bytes32 relayer)
73
- bytes4 constant Bounty = bytes4(keccak256("#bounty"));
74
52
  /// @dev Mutable node label - (uint id, bytes32 namespace, #string as name)
75
53
  bytes4 constant Label = bytes4(keccak256("#label"));
76
54
  /// @dev Block schema publication - (bytes4 key, #string as body, bytes32 name)
@@ -78,8 +56,6 @@ library Keys {
78
56
 
79
57
  /// @dev Structural status form - (uint code)
80
58
  bytes4 constant Status = bytes4(keccak256("#status"));
81
- /// @dev Structural asset amount form - (bytes32 asset, uint amount)
82
- bytes4 constant AssetAmount = bytes4(keccak256("#assetAmount"));
83
59
  /// @dev Structural account asset form - (bytes32 account, bytes32 asset)
84
60
  bytes4 constant AccountAsset = bytes4(keccak256("#accountAsset"));
85
61
  /// @dev Structural account amount form - (bytes32 account, bytes32 asset, uint amount)
@@ -0,0 +1,114 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import {Sizes, Specs} from "./Specs.sol";
5
+ import {Keys} from "./Keys.sol";
6
+
7
+ /// @notice Mutable reader over a block stream stored in memory.
8
+ /// All positions (`i`) are byte offsets relative to the start of `source`.
9
+ struct Reader {
10
+ /// @dev Current read position, relative to the source start.
11
+ uint i;
12
+ /// @dev Memory bytes containing the complete source region.
13
+ bytes source;
14
+ }
15
+
16
+ using Readers for Reader;
17
+
18
+ /// @title Readers
19
+ /// @notice Memory block stream parser for the rootzero protocol.
20
+ /// A `Reader` advances through an existing `bytes memory` source without copying its contents.
21
+ /// Blocks are encoded as `[bytes4 key][bytes4 payloadLen][payload]`.
22
+ library Readers {
23
+ /// @dev The current block has a truncated header or payload, an unexpected key,
24
+ /// or a payload size outside the accepted range.
25
+ error InvalidBlock();
26
+
27
+ /// @notice Create a reader backed by a memory byte array.
28
+ /// @param source Memory bytes containing the block stream.
29
+ /// @return cur Reader positioned at the beginning of `source`.
30
+ function open(bytes memory source) internal pure returns (Reader memory cur) {
31
+ cur.source = source;
32
+ }
33
+
34
+ /// @notice Return whether the reader has consumed its entire source.
35
+ /// @param cur Reader whose position should be checked.
36
+ /// @return Whether `cur.i` equals the source length.
37
+ function done(Reader memory cur) internal pure returns (bool) {
38
+ return cur.i == cur.source.length;
39
+ }
40
+
41
+ /// @notice Return whether the reader has bytes left to consume.
42
+ /// @param cur Reader whose position should be checked.
43
+ /// @return Whether `cur.i` differs from the source length.
44
+ function more(Reader memory cur) internal pure returns (bool) {
45
+ return cur.i != cur.source.length;
46
+ }
47
+
48
+ /// @notice Validate and consume the current block, advancing `cur.i` past it.
49
+ /// @param cur Reader to advance.
50
+ /// @param key Expected block key.
51
+ /// @param min Minimum payload length.
52
+ /// @param max Maximum payload length; zero means unbounded.
53
+ /// @return abs Absolute memory address of the payload start.
54
+ function consume(
55
+ Reader memory cur,
56
+ bytes4 key,
57
+ uint min,
58
+ uint max
59
+ ) private pure returns (uint abs) {
60
+ bytes memory source = cur.source;
61
+ uint i = cur.i;
62
+
63
+ if (i > source.length || source.length - i < Sizes.Header) revert InvalidBlock();
64
+
65
+ bytes4 current;
66
+ uint len;
67
+ assembly ("memory-safe") {
68
+ let header := mload(add(add(source, 0x20), i))
69
+ current := header
70
+ len := and(shr(192, header), 0xffffffff)
71
+ abs := add(add(source, 0x28), i)
72
+ }
73
+
74
+ if (current != key || len < min || (max != 0 && len > max)) revert InvalidBlock();
75
+ if (len > source.length - i - Sizes.Header) revert InvalidBlock();
76
+ cur.i = i + Sizes.Header + len;
77
+ }
78
+
79
+ /// @notice Validate and consume the current block described by `spec`.
80
+ function consume(Reader memory cur, uint spec) internal pure returns (uint abs) {
81
+ (bytes4 key, uint32 min, uint32 max) = Specs.decode(spec);
82
+ return consume(cur, key, min, max);
83
+ }
84
+
85
+ /// @notice Consume a BALANCE block and return its fields.
86
+ /// @param cur Reader; advanced past the block.
87
+ /// @return asset Asset identifier.
88
+ /// @return amount Token amount.
89
+ function unpackBalance(Reader memory cur) internal pure returns (bytes32 asset, uint amount) {
90
+ uint abs = consume(cur, Keys.Balance, 64, 64);
91
+ assembly ("memory-safe") {
92
+ asset := mload(abs)
93
+ amount := mload(add(abs, 0x20))
94
+ }
95
+ }
96
+
97
+ /// @notice Consume a TRANSACTION block and return its fields.
98
+ /// @param cur Reader; advanced past the block.
99
+ /// @return from Source account identifier.
100
+ /// @return to Destination account identifier.
101
+ /// @return asset Asset identifier.
102
+ /// @return amount Token amount.
103
+ function unpackTransaction(
104
+ Reader memory cur
105
+ ) internal pure returns (bytes32 from, bytes32 to, bytes32 asset, uint amount) {
106
+ uint abs = consume(cur, Keys.Transaction, 128, 128);
107
+ assembly ("memory-safe") {
108
+ from := mload(abs)
109
+ to := mload(add(abs, 0x20))
110
+ asset := mload(add(abs, 0x40))
111
+ amount := mload(add(abs, 0x60))
112
+ }
113
+ }
114
+ }
@@ -12,7 +12,7 @@ pragma solidity ^0.8.33;
12
12
  // - an empty schema string means the block has no structured payload
13
13
  // - commas separate siblings at every level
14
14
  // - braces define the current block payload body
15
- // - command requests are a single run when the request schema is non-empty
15
+ // - command inputs are a single run when the input schema is non-empty
16
16
  // - command state is a single active state run without trailing globals
17
17
  // - run items may repeat at top level for batching
18
18
  // - `maybe #x` marks an optional block item
@@ -36,11 +36,11 @@ pragma solidity ^0.8.33;
36
36
  // - generic lists use the stable key derived from `#list`
37
37
  // - standard keys are derived from block aliases, e.g. bytes4(keccak256("#amount"))
38
38
  // - custom keys are opaque bytes4 tags and only need to be unique in their
39
- // active context; use `Schema(host, key, schema, name)` to publish their meaning
39
+ // active context; use `Schema(host, spec, schema, name)` to publish their meaning
40
40
  // - see `docs/Schema.md` for the full working spec
41
41
  //
42
42
  // Pipeline state:
43
- // - command request and state streams are each a single run of blocks under the
43
+ // - command input and state streams are each a single run of blocks under the
44
44
  // current protocol convention; the block format may support other shapes in
45
45
  // future protocol surfaces
46
46
  // - `balance(...)` and `custody(...)` are live, linear state in the active command pipeline
@@ -48,102 +48,60 @@ pragma solidity ^0.8.33;
48
48
  // - while a balance or custody is in-flight as pipeline state, it is not simultaneously persisted
49
49
  // in another ledger/store by this protocol
50
50
  // - commands must preserve, transform, settle, or intentionally consume pipeline state
51
- // - request blocks such as `amount(...)`, `allocation(...)`, and `allowance(...)`
51
+ // - input blocks such as `amount(...)`, `allocation(...)`, and `allowance(...)`
52
52
  // express intent, constraints, or references
53
- // - request and value/response blocks are not live state
53
+ // - input and value/response blocks are not live state
54
54
  //
55
- // Signed blocks:
56
- // - an authenticated input segment ends with one trailing AUTH block
57
- // - only the final AUTH is treated specially; earlier AUTH blocks remain ordinary signed bytes
58
- // - the signed slice runs from the segment start through the AUTH head, excluding only AUTH proof bytes
59
- // - `cid` binds the signature to one command; `deadline` acts as expiry and nonce
60
- // - current helpers assume proof layout `[bytes20 signer][bytes65 sig]`
61
-
62
55
  /// @title Schemas
63
56
  /// @notice Human-readable schema string constants for each block type.
64
57
  /// These strings describe payload layout for discovery events and docs; block
65
58
  /// aliases map to standard keys by convention. Custom blocks may use any unique
66
59
  /// bytes4 key in their active context.
67
60
  library Schemas {
61
+ // Empty and reserved payloads
62
+
68
63
  string constant Unit = "";
69
- string constant Node = "{ uint id }";
70
- string constant Account = "{ bytes32 account }";
71
- string constant Asset = "{ bytes32 asset }";
72
- string constant Amount = "{ bytes32 asset, uint amount }";
73
- string constant Balance = "{ bytes32 asset, uint amount }";
74
- string constant BalanceLimit = "{ bytes32 asset, uint min, uint max }";
75
- string constant Custody = "{ uint host, bytes32 asset, uint amount }";
76
- string constant CustodyLimit = "{ uint host, bytes32 asset, uint min, uint max }";
77
- string constant Allocation = "{ uint host, bytes32 asset, uint amount }";
78
- string constant Allowance = "{ uint host, bytes32 asset, uint amount }";
79
- string constant Transaction = "{ bytes32 from, bytes32 to, bytes32 asset, uint amount }";
80
- string constant Context = "{ bytes32 account, #bytes as state, #bytes as request }";
81
- string constant Recover = "{ uint handler, uint resources, bytes32 key, #bytes as witness }";
82
- string constant Call = "{ uint target, uint resources, #bytes as payload }";
83
- string constant Step = "{ uint target, uint resources, #bytes as request }";
84
- string constant Relay = "{ uint portal, uint resources, #bytes as request }";
85
- string constant Dispatch = "{ uint portal, uint resources, #bytes as payload }";
86
- string constant Bounty = "{ uint amount, bytes32 relayer }";
87
- string constant Fee = "{ uint amount }";
88
- string constant Auth = "{ uint cid, uint deadline, #bytes as proof }";
89
- string constant Label = "{ uint id, bytes32 namespace, #string as name }";
90
- string constant Schema = "{ bytes4 key, #string as body, bytes32 name }";
91
64
  string constant Bytes = "";
92
65
  string constant String = "";
93
66
  string constant List = "";
94
67
  string constant Evm = "";
95
- }
96
68
 
97
- /// @title Forms
98
- /// @notice Reusable structural block schemas for core tuple shapes.
99
- /// These describe payload form without assigning command or query semantics.
100
- library Forms {
69
+ // One-word payloads
70
+
71
+ string constant Node = "{ uint id }";
72
+ string constant Account = "{ bytes32 account }";
73
+ string constant Asset = "{ bytes32 asset }";
101
74
  string constant Status = "{ uint code }";
102
- string constant AssetAmount = "{ bytes32 asset, uint amount }";
75
+
76
+ // Two-word payloads
77
+
78
+ string constant Amount = "{ bytes32 asset, uint amount }";
79
+ string constant Balance = "{ bytes32 asset, uint amount }";
103
80
  string constant AccountAsset = "{ bytes32 account, bytes32 asset }";
81
+
82
+ // Three-word payloads
83
+
84
+ string constant Allocation = "{ uint host, bytes32 asset, uint amount }";
85
+ string constant Allowance = "{ uint host, bytes32 asset, uint amount }";
86
+ string constant Custody = "{ uint host, bytes32 asset, uint amount }";
104
87
  string constant AccountAmount = "{ bytes32 account, bytes32 asset, uint amount }";
105
88
  string constant HostAmount = "{ uint host, bytes32 asset, uint amount }";
106
89
  string constant HostAccountAsset = "{ uint host, bytes32 account, bytes32 asset }";
90
+
91
+ // Four-word payloads
92
+
93
+ string constant Transaction = "{ bytes32 from, bytes32 to, bytes32 asset, uint amount }";
107
94
  string constant HostAccountAmount = "{ uint host, bytes32 account, bytes32 asset, uint amount }";
108
- }
109
95
 
110
- /// @title Sizes
111
- /// @notice Total byte sizes for fixed-width block types, including the 8-byte header (4-byte key + 4-byte payloadLen).
112
- library Sizes {
113
- /// @dev Shared block header size: 4-byte key + 4-byte payload length.
114
- uint constant Header = 8;
115
- /// @dev One fixed-width payload word.
116
- uint constant Word = 32;
117
- /// @dev 8 header + 32 payload = 40 bytes total.
118
- uint constant B32 = Header + Word;
119
- /// @dev 8 header + 64 payload = 72 bytes total.
120
- uint constant B64 = Header + 2 * Word;
121
- /// @dev 8 header + 96 payload = 104 bytes total.
122
- uint constant B96 = Header + 3 * Word;
123
- /// @dev 8 header + 128 payload = 136 bytes total.
124
- uint constant B128 = Header + 4 * Word;
125
- /// @dev 8 header + 160 payload = 168 bytes total.
126
- uint constant B160 = Header + 5 * Word;
127
- /// @dev AUTH proof segment only: 20-byte signer + 65-byte signature = 85 bytes
128
- uint constant Proof = 85;
129
- /// @dev AUTH block: 8 header + 32 cid + 32 deadline + nested BYTES block with 85-byte proof = 165 bytes
130
- uint constant Auth = B64 + Header + Proof;
131
- /// @dev STATUS block: 8 header + 32 status code = 40 bytes
132
- uint constant Status = B32;
133
- /// @dev AMOUNT block: 8 header + 32 asset + 32 amount = 72 bytes
134
- uint constant Amount = B64;
135
- /// @dev BALANCE block: 8 header + 32 asset + 32 amount = 72 bytes
136
- uint constant Balance = B64;
137
- /// @dev BALANCE_LIMIT block: 8 header + 32 asset + 32 min + 32 max = 104 bytes
138
- uint constant BalanceLimit = B96;
139
- /// @dev FEE block: 8 header + 32 amount = 40 bytes
140
- uint constant Fee = B32;
141
- /// @dev BOUNTY block: 8 header + 32 amount + 32 relayer = 72 bytes
142
- uint constant Bounty = B64;
143
- /// @dev ALLOCATION/CUSTODY block: 8 header + 32 host + 32 asset + 32 amount = 104 bytes
144
- uint constant HostAmount = B96;
145
- /// @dev CUSTODY_LIMIT block: 8 header + 32 host + 32 asset + 32 min + 32 max = 136 bytes
146
- uint constant CustodyLimit = B128;
147
- /// @dev TRANSACTION block: 8 header + 32 from + 32 to + 32 asset + 32 amount = 136 bytes
148
- uint constant Transaction = B128;
96
+ // Composite payloads
97
+
98
+ string constant Call = "{ uint target, uint resources, #bytes as payload }";
99
+ string constant Step = "{ uint cmd, uint resources, #bytes as input }";
100
+ string constant Relay = "{ uint portal, uint resources, #bytes as input }";
101
+ string constant Dispatch = "{ uint portal, uint resources, #bytes as payload }";
102
+ string constant Context = "{ bytes32 account, #bytes as state, #bytes as input }";
103
+ string constant Recover = "{ uint handler, uint resources, bytes32 key, #bytes as witness }";
104
+ string constant Label = "{ uint id, bytes32 namespace, #string as name }";
105
+ string constant Schema = "{ uint spec, #string as body, bytes32 name }";
149
106
  }
107
+
@@ -0,0 +1,218 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import {Keys} from "./Keys.sol";
5
+ import {max24, replace8, replace32} from "../utils/Utils.sol";
6
+
7
+ /// @title Sizes
8
+ /// @notice Total byte sizes for fixed-width block types, including the 8-byte header (4-byte key + 4-byte payloadLen).
9
+ library Sizes {
10
+ /// @dev Shared block header size: 4-byte key + 4-byte payload length.
11
+ uint constant Header = 8;
12
+ /// @dev One fixed-width payload word.
13
+ uint constant Word = 32;
14
+ /// @dev 8 header + 32 payload = 40 bytes total.
15
+ uint constant B32 = Header + Word;
16
+ /// @dev 8 header + 64 payload = 72 bytes total.
17
+ uint constant B64 = Header + 2 * Word;
18
+ /// @dev 8 header + 96 payload = 104 bytes total.
19
+ uint constant B96 = Header + 3 * Word;
20
+ /// @dev 8 header + 128 payload = 136 bytes total.
21
+ uint constant B128 = Header + 4 * Word;
22
+ /// @dev 8 header + 160 payload = 168 bytes total.
23
+ uint constant B160 = Header + 5 * Word;
24
+ /// @dev STATUS block: 8 header + 32 status code = 40 bytes
25
+ uint constant Status = B32;
26
+ /// @dev AMOUNT block: 8 header + 32 asset + 32 amount = 72 bytes
27
+ uint constant Amount = B64;
28
+ /// @dev BALANCE block: 8 header + 32 asset + 32 amount = 72 bytes
29
+ uint constant Balance = B64;
30
+ /// @dev ALLOCATION/CUSTODY block: 8 header + 32 host + 32 asset + 32 amount = 104 bytes
31
+ uint constant HostAmount = B96;
32
+ /// @dev TRANSACTION block: 8 header + 32 from + 32 to + 32 asset + 32 amount = 136 bytes
33
+ uint constant Transaction = B128;
34
+ }
35
+
36
+ /// @title Specs
37
+ /// @notice Word-aligned block specifications encoded as
38
+ /// `[key:4][min:4][max:4][hint:3][stride:1][container:4][reserved:12]`.
39
+ /// The upper eight bytes of a fixed-layout spec are its encoded block header,
40
+ /// allowing the entire spec word to be written directly as that header.
41
+ /// A maximum of zero means unbounded and requires a growable writer.
42
+ library Specs {
43
+ /// @dev A payload is incompatible with its block specification.
44
+ error InvalidSpec();
45
+ /// @dev A direct specification cannot contain a wrapper block.
46
+ error InvalidContainer();
47
+
48
+ uint private constant SizeFields = (uint(1) << 192) | (uint(1) << 160) | (uint(1) << 136);
49
+
50
+ // Reusable field shapes keep public specs readable while remaining valid
51
+ // compile-time constant expressions.
52
+ uint private constant Exact32 = 32 * SizeFields;
53
+ uint private constant Exact64 = 64 * SizeFields;
54
+ uint private constant Exact96 = 96 * SizeFields;
55
+ uint private constant Exact128 = 128 * SizeFields;
56
+ uint private constant UnboundedHint128 = uint(128) << 136;
57
+ uint private constant UnboundedMin72Hint256 = (uint(72) << 192) | (uint(256) << 136);
58
+ uint private constant UnboundedMin48Hint512 = (uint(48) << 192) | (uint(512) << 136);
59
+ uint private constant UnboundedMin104Hint256 = (uint(104) << 192) | (uint(256) << 136);
60
+
61
+ uint constant Empty = uint(bytes32(Keys.Empty));
62
+ uint constant Any = uint(bytes32(Keys.Any)) | UnboundedHint128;
63
+ uint constant Amount = uint(bytes32(Keys.Amount)) | Exact64;
64
+ uint constant Balance = uint(bytes32(Keys.Balance)) | Exact64;
65
+ uint constant Allocation = uint(bytes32(Keys.Allocation)) | Exact96;
66
+ uint constant Allowance = uint(bytes32(Keys.Allowance)) | Exact96;
67
+ uint constant Custody = uint(bytes32(Keys.Custody)) | Exact96;
68
+ uint constant List = uint(bytes32(Keys.List)) | UnboundedHint128;
69
+ uint constant Evm = uint(bytes32(Keys.Evm)) | UnboundedHint128;
70
+ uint constant Bytes = uint(bytes32(Keys.Bytes)) | UnboundedHint128;
71
+ uint constant String = uint(bytes32(Keys.String)) | UnboundedHint128;
72
+ uint constant Account = uint(bytes32(Keys.Account)) | Exact32;
73
+ uint constant Transaction = uint(bytes32(Keys.Transaction)) | Exact128;
74
+ uint constant Step = uint(bytes32(Keys.Step)) | UnboundedMin72Hint256;
75
+ uint constant Relay = uint(bytes32(Keys.Relay)) | UnboundedMin72Hint256;
76
+ uint constant Context = uint(bytes32(Keys.Context)) | UnboundedMin48Hint512;
77
+ uint constant Recover = uint(bytes32(Keys.Recover)) | UnboundedMin104Hint256;
78
+ uint constant Dispatch = uint(bytes32(Keys.Dispatch)) | UnboundedMin72Hint256;
79
+ uint constant Call = uint(bytes32(Keys.Call)) | UnboundedMin72Hint256;
80
+ uint constant Asset = uint(bytes32(Keys.Asset)) | Exact32;
81
+ uint constant Node = uint(bytes32(Keys.Node)) | Exact32;
82
+ uint constant Label = uint(bytes32(Keys.Label)) | UnboundedMin72Hint256;
83
+ uint constant Schema = uint(bytes32(Keys.Schema)) | UnboundedMin72Hint256;
84
+
85
+ uint constant Status = uint(bytes32(Keys.Status)) | Exact32;
86
+ uint constant AccountAsset = uint(bytes32(Keys.AccountAsset)) | Exact64;
87
+ uint constant AccountAmount = uint(bytes32(Keys.AccountAmount)) | Exact96;
88
+ uint constant HostAmount = uint(bytes32(Keys.HostAmount)) | Exact96;
89
+ uint constant HostAccountAsset = uint(bytes32(Keys.HostAccountAsset)) | Exact96;
90
+ uint constant HostAccountAmount = uint(bytes32(Keys.HostAccountAmount)) | Exact128;
91
+
92
+ /// @notice Construct a block specification from its encoded fields.
93
+ /// @param blockkey Encoded block key.
94
+ /// @param min Minimum accepted payload length.
95
+ /// @param max Maximum accepted payload length; zero means unbounded.
96
+ /// @param hint Initial per-block payload capacity.
97
+ /// @return spec Packed block specification.
98
+ function create(bytes4 blockkey, uint32 min, uint32 max, uint32 hint) internal pure returns (uint spec) {
99
+ spec |= uint(uint32(blockkey)) << 224;
100
+ spec |= uint(min) << 192;
101
+ spec |= uint(max) << 160;
102
+ spec |= uint(max24(hint)) << 136;
103
+ }
104
+
105
+ /// @notice Decode the block key and accepted payload range from `spec`.
106
+ /// @param spec Packed block specification.
107
+ /// @return blockkey Encoded block key.
108
+ /// @return min Minimum accepted payload length.
109
+ /// @return max Maximum accepted payload length; zero means unbounded.
110
+ function decode(uint spec) internal pure returns (bytes4 blockkey, uint32 min, uint32 max) {
111
+ blockkey = bytes4(uint32(spec >> 224));
112
+ min = uint32(spec >> 192);
113
+ max = uint32(spec >> 160);
114
+ }
115
+
116
+ /// @notice Return the block key encoded in `spec`.
117
+ /// @param spec Packed block specification.
118
+ /// @return Encoded block key.
119
+ function key(uint spec) internal pure returns (bytes4) {
120
+ return bytes4(uint32(spec >> 224));
121
+ }
122
+
123
+ /// @notice Return the raw stride encoded in `spec`.
124
+ /// @dev Contextual defaults are applied by helpers such as `count`.
125
+ /// @param spec Packed block specification.
126
+ /// @return Encoded block stride.
127
+ function stride(uint spec) internal pure returns (uint8) {
128
+ return uint8(spec >> 128);
129
+ }
130
+
131
+ /// @notice Return the canonical form of `spec` with implicit defaults resolved.
132
+ /// @param spec Packed block specification.
133
+ /// @param direct Whether the specification must not contain a wrapper block.
134
+ /// @dev A present spec with an encoded zero stride receives stride one;
135
+ /// the empty spec remains unchanged.
136
+ /// @return Canonical specification.
137
+ function normalize(uint spec, bool direct) internal pure returns (uint) {
138
+ if (direct && uint32(spec >> 96) != 0) revert InvalidContainer();
139
+ if (stride(spec) == 0 && key(spec) != bytes4(0)) spec |= uint(1) << 128;
140
+ return spec;
141
+ }
142
+
143
+ /// @notice Return the effective outer block key and optional wrapped child key.
144
+ /// @dev A direct spec returns its own key as `outer` and a zero `child`.
145
+ /// @param spec Packed block specification.
146
+ /// @return outer Effective outer block key.
147
+ /// @return child Wrapped child key, or zero for a direct specification.
148
+ function keys(uint spec) internal pure returns (bytes4 outer, bytes4 child) {
149
+ child = key(spec);
150
+ outer = bytes4(uint32(spec >> 96));
151
+
152
+ if (outer == bytes4(0)) {
153
+ outer = child;
154
+ child = bytes4(0);
155
+ }
156
+ }
157
+
158
+ /// @notice Return whether a payload size lies within a specification's bounds.
159
+ /// @param spec Packed block specification.
160
+ /// @param size Payload length to test.
161
+ /// @return Whether the payload length is accepted.
162
+ function accepts(uint spec, uint size) internal pure returns (bool) {
163
+ uint32 min = uint32(spec >> 192);
164
+ uint32 max = uint32(spec >> 160);
165
+ return size >= min && (max == 0 || size <= max);
166
+ }
167
+
168
+ /// @notice Validate a payload size against a specification's bounds.
169
+ /// @param spec Packed block specification.
170
+ /// @param size Payload length to validate.
171
+ function validate(uint spec, uint size) internal pure {
172
+ if (!accepts(spec, size)) revert InvalidSpec();
173
+ }
174
+
175
+ /// @notice Return an exact payload size constrained to an accepted range.
176
+ /// @param spec Packed block specification.
177
+ /// @param min Smallest exact size accepted by the caller.
178
+ /// @param max Largest exact size accepted by the caller.
179
+ /// @return size Exact payload size encoded by the specification.
180
+ function exact(uint spec, uint min, uint max) internal pure returns (uint size) {
181
+ size = uint32(spec >> 192);
182
+ if (size != uint32(spec >> 160) || size < min || size > max) revert InvalidSpec();
183
+ }
184
+
185
+ /// @notice Return the number of blocks represented by `groups`.
186
+ /// @param spec Packed block specification.
187
+ /// @param groups Number of groups.
188
+ /// @return Number of blocks across all groups.
189
+ function count(uint spec, uint groups) internal pure returns (uint) {
190
+ return groups * stride(normalize(spec, false));
191
+ }
192
+
193
+ /// @notice Return the buffer configuration for `groups` of `spec`.
194
+ /// @dev Dynamic specs use their allocation hint and a maximum of zero means growable.
195
+ /// @param spec Packed block specification.
196
+ /// @param groups Number of groups to allocate.
197
+ /// @return capacity Initial encoded byte capacity.
198
+ /// @return growable Whether the buffer may grow beyond its initial capacity.
199
+ function allocation(uint spec, uint groups) internal pure returns (uint capacity, bool growable) {
200
+ capacity = count(spec, groups) * (Sizes.Header + uint24(spec >> 136));
201
+ growable = uint32(spec >> 160) == 0;
202
+ }
203
+
204
+ /// @notice Return `spec` annotated as a generic LIST item.
205
+ /// @param spec Child block specification.
206
+ /// @return Specification wrapped in a LIST container.
207
+ function many(uint spec) internal pure returns (uint) {
208
+ return replace32(spec, 96, uint32(key(List)));
209
+ }
210
+
211
+ /// @notice Return `spec` grouped with an explicit stride.
212
+ /// @param spec Packed block specification.
213
+ /// @param n Number of blocks per group.
214
+ /// @return Specification with its stride replaced by `n`.
215
+ function group(uint spec, uint8 n) internal pure returns (uint) {
216
+ return replace8(spec, 128, n);
217
+ }
218
+ }