@rootzero/contracts 0.9.3 → 0.9.5

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 (49) hide show
  1. package/Commands.sol +3 -2
  2. package/Core.sol +4 -3
  3. package/Cursors.sol +1 -1
  4. package/Events.sol +2 -3
  5. package/README.md +18 -24
  6. package/blocks/Cursors.sol +316 -337
  7. package/blocks/Keys.sol +40 -57
  8. package/blocks/Schema.sol +57 -208
  9. package/blocks/Writers.sol +376 -135
  10. package/commands/Base.sol +6 -48
  11. package/commands/Burn.sol +2 -2
  12. package/commands/Credit.sol +3 -3
  13. package/commands/Debit.sol +3 -2
  14. package/commands/Deposit.sol +11 -8
  15. package/commands/Provision.sol +11 -8
  16. package/commands/Transfer.sol +2 -2
  17. package/commands/Withdraw.sol +3 -3
  18. package/commands/admin/AllowAssets.sol +1 -1
  19. package/commands/admin/Allowance.sol +1 -1
  20. package/commands/admin/Authorize.sol +1 -1
  21. package/commands/admin/DenyAssets.sol +1 -1
  22. package/commands/admin/Execute.sol +7 -6
  23. package/commands/admin/Unauthorize.sol +1 -1
  24. package/core/Access.sol +11 -0
  25. package/core/Calls.sol +5 -5
  26. package/core/Context.sol +3 -4
  27. package/core/Host.sol +25 -20
  28. package/core/Payable.sol +57 -0
  29. package/core/Pipeline.sol +55 -0
  30. package/docs/Schema.md +196 -0
  31. package/events/Command.sol +1 -2
  32. package/events/Introduction.sol +22 -0
  33. package/events/{Piped.sol → Rooted.sol} +3 -3
  34. package/package.json +2 -2
  35. package/peer/AllowAssets.sol +1 -1
  36. package/peer/Allowance.sol +1 -1
  37. package/peer/BalancePull.sol +1 -1
  38. package/peer/DenyAssets.sol +1 -1
  39. package/peer/Pipe.sol +38 -0
  40. package/peer/Settle.sol +1 -1
  41. package/queries/Assets.sol +4 -3
  42. package/queries/Balances.sol +2 -1
  43. package/queries/Positions.sol +12 -12
  44. package/utils/Value.sol +8 -14
  45. package/commands/Pipe.sol +0 -67
  46. package/docs/GETTING_STARTED.md +0 -294
  47. package/events/Governed.sol +0 -21
  48. package/events/Host.sol +0 -22
  49. package/interfaces/IHostDiscovery.sol +0 -16
@@ -4,7 +4,6 @@ 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 {Writer, Writers} from "./Writers.sol";
8
7
 
9
8
  /// @notice Zero-copy view into a calldata block stream.
10
9
  /// All positions (`i`, `bound`) are byte offsets relative to the start of the source region.
@@ -47,6 +46,7 @@ library Cursors {
47
46
  error UnexpectedValue();
48
47
  /// @dev Prime block counts are not divisible by, or do not match, their declared group sizes.
49
48
  error BadRatio();
49
+
50
50
  // -------------------------------------------------------------------------
51
51
  // Cursor construction and navigation
52
52
  // -------------------------------------------------------------------------
@@ -65,6 +65,17 @@ library Cursors {
65
65
  cur.len = source.length;
66
66
  }
67
67
 
68
+ /// @notice Create a cursor and prime it for a grouped iteration pass.
69
+ /// Equivalent to `open(source)` followed by `primeRun(group)`.
70
+ /// @param source Calldata slice that forms the block stream.
71
+ /// @param group Expected block group size (e.g. 1 for single, 2 for paired).
72
+ /// @return cur Cursor with `bound` set to the end of the first run.
73
+ /// @return groups Number of block groups in the run (`prime block count / group`).
74
+ function init(bytes calldata source, uint group) internal pure returns (Cur memory cur, uint groups) {
75
+ cur = open(source);
76
+ (, groups) = cur.primeRun(group);
77
+ }
78
+
68
79
  /// @notice Move the cursor to an absolute position within the source region.
69
80
  /// @param cur Cursor to update.
70
81
  /// @param i New read position (byte offset relative to source start).
@@ -75,6 +86,26 @@ library Cursors {
75
86
  return cur;
76
87
  }
77
88
 
89
+ /// @notice Advance the cursor by `n` bytes within the source region.
90
+ /// Reverts with `IncompleteCursor` if `n` exceeds the remaining cursor region length.
91
+ /// @param cur Cursor to advance.
92
+ /// @param n Number of bytes to skip from the current read position.
93
+ function skip(Cur memory cur, uint n) internal pure returns (Cur memory) {
94
+ if (n > cur.len - cur.i) revert IncompleteCursor();
95
+ cur.i += n;
96
+ return cur;
97
+ }
98
+
99
+ /// @notice Advance the cursor to a later absolute position within the source region.
100
+ /// Reverts with `IncompleteCursor` if `i` exceeds the cursor region length or is before `cur.i`.
101
+ /// @param cur Cursor to advance.
102
+ /// @param i New read position (byte offset relative to source start).
103
+ function skipTo(Cur memory cur, uint i) internal pure returns (Cur memory) {
104
+ if (i > cur.len || cur.i > i) revert IncompleteCursor();
105
+ cur.i = i;
106
+ return cur;
107
+ }
108
+
78
109
  /// @notice Create a subcursor over the half-open range `[from, to)` within the source region.
79
110
  /// The returned cursor starts at position zero within that sliced region.
80
111
  /// @param cur Source cursor.
@@ -103,7 +134,19 @@ library Cursors {
103
134
  /// @param to End byte offset within the source region (exclusive).
104
135
  /// @return data Calldata view over the requested sub-range.
105
136
  function raw(Cur memory cur, uint from, uint to) internal pure returns (bytes calldata data) {
106
- data = cur.slice(from, to).raw();
137
+ if (from > to || to > cur.len) revert MalformedBlocks();
138
+ if (cur.len > msg.data.length || cur.offset > msg.data.length - cur.len) revert MalformedBlocks();
139
+ data = msg.data[cur.offset + from:cur.offset + to];
140
+ }
141
+
142
+ /// @notice Hash a sub-range of the cursor region.
143
+ /// Does not advance the cursor; `from` and `to` are relative to the source region.
144
+ /// @param cur Source cursor.
145
+ /// @param from Start byte offset within the source region (inclusive).
146
+ /// @param to End byte offset within the source region (exclusive).
147
+ /// @return digest Keccak256 hash of the requested sub-range.
148
+ function hash(Cur memory cur, uint from, uint to) internal pure returns (bytes32 digest) {
149
+ digest = keccak256(cur.raw(from, to));
107
150
  }
108
151
 
109
152
  /// @notice Read a block header at position `i` without advancing the cursor.
@@ -112,11 +155,11 @@ library Cursors {
112
155
  /// @return key Four-byte block type identifier.
113
156
  /// @return len Payload byte length declared in the header.
114
157
  function peek(Cur memory cur, uint i) internal pure returns (bytes4 key, uint len) {
115
- if (i + 8 > cur.len) revert MalformedBlocks();
158
+ if (i + Sizes.Header > cur.len) revert MalformedBlocks();
116
159
  uint abs = cur.offset + i;
117
160
  key = bytes4(msg.data[abs:abs + 4]);
118
161
  len = uint32(bytes4(msg.data[abs + 4:abs + 8]));
119
- if (i + 8 + len > cur.len) revert MalformedBlocks();
162
+ if (i + Sizes.Header + len > cur.len) revert MalformedBlocks();
120
163
  }
121
164
 
122
165
  /// @notice Return the byte offset immediately past the block at the current cursor position.
@@ -128,34 +171,49 @@ library Cursors {
128
171
  return cur.i + Sizes.Header + len;
129
172
  }
130
173
 
174
+ /// @notice Return true if position `i` is at a block header with the given key.
175
+ /// Returns false when `i` is out of bounds or the key differs.
176
+ /// @param cur Source cursor.
177
+ /// @param i Byte offset of the block header within the source region.
178
+ /// @param key Expected block type identifier.
179
+ /// @return Whether the block header at `i` uses `key`.
180
+ function hasAt(Cur memory cur, uint i, bytes4 key) internal pure returns (bool) {
181
+ if (i > cur.len || Sizes.Header > cur.len - i) return false;
182
+ uint abs = cur.offset + i;
183
+ return bytes4(msg.data[abs:abs + 4]) == key;
184
+ }
185
+
131
186
  /// @notice Return true if the current cursor position is at a block header with the given key.
132
187
  /// Returns false when `cur.i` is out of bounds or the key differs.
133
188
  /// @param cur Source cursor.
134
189
  /// @param key Expected block type identifier.
135
190
  /// @return Whether the block header at `cur.i` uses `key`.
136
191
  function isAt(Cur memory cur, bytes4 key) internal pure returns (bool) {
137
- if (cur.i + 8 > cur.len) return false;
138
- uint abs = cur.offset + cur.i;
139
- return bytes4(msg.data[abs:abs + 4]) == key;
192
+ return cur.hasAt(cur.i, key);
140
193
  }
141
194
 
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;
195
+ /// @notice Enter a block at the current position and return its next offset.
196
+ /// Advances `cur.i` past the block header so the payload can be parsed
197
+ /// directly from the same cursor. The returned `next` is the byte offset
198
+ /// immediately after the block payload, relative to the current cursor region.
199
+ /// @param cur Cursor positioned at the expected block; advanced past the 8-byte header.
200
+ /// @param key Expected block key.
201
+ /// @param min Minimum acceptable payload length.
202
+ /// @param max Maximum acceptable payload length; 0 means unbounded.
203
+ /// @return next Byte offset immediately after the block payload.
204
+ function enter(Cur memory cur, bytes4 key, uint min, uint max) internal pure returns (uint next) {
205
+ (bytes4 current, uint len) = peek(cur, cur.i);
206
+ if (current != key) revert InvalidBlock();
207
+ if (len < min || (max != 0 && len > max)) revert InvalidBlock();
208
+ next = cur.i + Sizes.Header + len;
209
+ cur.i += Sizes.Header;
153
210
  }
154
211
 
155
212
  /// @notice Validate a block at position `i` and return its payload location.
156
213
  /// Does not advance the cursor.
157
214
  /// @param cur Source cursor.
158
215
  /// @param i Byte offset of the block within the source region.
216
+ /// @param end Required next offset after the block; 0 means no exact-end check.
159
217
  /// @param key Expected block type key; reverts if actual key differs.
160
218
  /// @param min Minimum acceptable payload length (inclusive).
161
219
  /// @param max Maximum acceptable payload length (inclusive); 0 means unbounded.
@@ -164,15 +222,30 @@ library Cursors {
164
222
  function expect(
165
223
  Cur memory cur,
166
224
  uint i,
225
+ uint end,
167
226
  bytes4 key,
168
227
  uint min,
169
228
  uint max
170
229
  ) internal pure returns (uint abs, uint next) {
171
230
  (bytes4 current, uint len) = peek(cur, i);
172
231
  if (current != key) revert InvalidBlock();
173
- abs = cur.offset + i + 8;
174
- next = i + 8 + len;
175
232
  if (len < min || (max != 0 && len > max)) revert InvalidBlock();
233
+ abs = cur.offset + i + Sizes.Header;
234
+ next = i + Sizes.Header + len;
235
+ if (end != 0 && next != end) revert IncompleteCursor();
236
+ }
237
+
238
+ /// @notice Validate and consume the current block, advancing `cur.i` past it.
239
+ /// @param cur Cursor to advance.
240
+ /// @param end Required next offset after the block; 0 means no exact-end check.
241
+ /// @param key Expected block type key.
242
+ /// @param min Minimum payload length.
243
+ /// @param max Maximum payload length (0 = unbounded).
244
+ /// @return abs Absolute calldata offset of the payload start.
245
+ function consume(Cur memory cur, uint end, bytes4 key, uint min, uint max) internal pure returns (uint abs) {
246
+ uint next;
247
+ (abs, next) = expect(cur, cur.i, end, key, min, max);
248
+ cur.i = next;
176
249
  }
177
250
 
178
251
  /// @notice Count consecutive blocks of the same key starting at `i`.
@@ -186,7 +259,7 @@ library Cursors {
186
259
  while (next < cur.len) {
187
260
  (bytes4 current, uint len) = peek(cur, next);
188
261
  if (current != key) break;
189
- next += 8 + len;
262
+ next += Sizes.Header + len;
190
263
 
191
264
  unchecked {
192
265
  ++total;
@@ -197,20 +270,19 @@ library Cursors {
197
270
  /// @notice Initialise the cursor for a grouped iteration pass.
198
271
  /// Reads the key of the first block, counts the consecutive run of that key,
199
272
  /// stores the run end in `cur.bound`, validates that the count is a
200
- /// multiple of `group`, and returns both the raw block count and the
201
- /// normalized quotient (`count / group`).
273
+ /// multiple of `group`, and returns the run key and normalized group count.
202
274
  /// @param cur Cursor to prime; `cur.bound` is updated in place.
203
275
  /// @param group Expected group size (e.g. 1 for single-asset, 2 for paired input/output).
204
276
  /// @return key Block type identifier of the run.
205
- /// @return count Total number of blocks in the run (always a multiple of `group`).
206
- /// @return quotient Number of groups represented by the run (`count / group`).
207
- function primeRun(Cur memory cur, uint group) internal pure returns (bytes4 key, uint count, uint quotient) {
277
+ /// @return groups Number of groups represented by the run (`block count / group`).
278
+ function primeRun(Cur memory cur, uint group) internal pure returns (bytes4 key, uint groups) {
208
279
  if (group == 0) revert ZeroGroup();
209
280
  key = cur.i + 4 > cur.len ? bytes4(0) : bytes4(msg.data[cur.offset + cur.i:cur.offset + cur.i + 4]);
281
+ uint count;
210
282
  (count, cur.bound) = countRun(cur, cur.i, key);
211
283
  if (count == 0) revert ZeroCursor();
212
284
  if (count % group != 0) revert BadRatio();
213
- quotient = count / group;
285
+ groups = count / group;
214
286
  }
215
287
 
216
288
  /// @notice Scan forward from `i` for the first block matching `key`.
@@ -222,7 +294,7 @@ library Cursors {
222
294
  while (i < cur.len) {
223
295
  (bytes4 current, uint len) = peek(cur, i);
224
296
  if (current == key) return i;
225
- i += 8 + len;
297
+ i += Sizes.Header + len;
226
298
  }
227
299
  return cur.len;
228
300
  }
@@ -235,29 +307,6 @@ library Cursors {
235
307
  return find(cur, cur.i, key);
236
308
  }
237
309
 
238
- /// @notice Validate and consume the current block, advancing `cur.i` past it.
239
- /// @param cur Cursor to advance.
240
- /// @param key Expected block type key.
241
- /// @param min Minimum payload length.
242
- /// @param max Maximum payload length (0 = unbounded).
243
- /// @return abs Absolute calldata offset of the payload start.
244
- function consume(Cur memory cur, bytes4 key, uint min, uint max) internal pure returns (uint abs) {
245
- uint next;
246
- (abs, next) = expect(cur, cur.i, key, min, max);
247
- cur.i = next;
248
- }
249
-
250
- /// @notice Enter a Bundle block at the current position and return the next offset.
251
- /// Advances `cur.i` past the bundle header so the bundled members can be parsed
252
- /// directly from the same cursor. The returned `next` is the byte offset
253
- /// immediately after the bundle payload, relative to the current cursor region.
254
- /// @param cur Cursor positioned at a bundle block; advanced past the 8-byte header.
255
- /// @return next Byte offset immediately after the bundle payload.
256
- function bundle(Cur memory cur) internal pure returns (uint next) {
257
- (, next) = expect(cur, cur.i, Keys.Bundle, 0, 0);
258
- cur.i += 8;
259
- }
260
-
261
310
  /// @notice Enter a List block at the current position and return the next offset.
262
311
  /// Advances `cur.i` past the list header so the list members can be parsed
263
312
  /// directly from the same cursor. The returned `next` is the byte offset
@@ -265,8 +314,7 @@ library Cursors {
265
314
  /// @param cur Cursor positioned at a list block; advanced past the 8-byte header.
266
315
  /// @return next Byte offset immediately after the list payload.
267
316
  function list(Cur memory cur) internal pure returns (uint next) {
268
- (, next) = expect(cur, cur.i, Keys.List, 0, 0);
269
- cur.i += 8;
317
+ next = enter(cur, Keys.List, 0, 0);
270
318
  }
271
319
 
272
320
  /// @notice Consume a block with the given key at the current position and return a cursor over the full block slice.
@@ -276,11 +324,24 @@ library Cursors {
276
324
  /// @param key Expected block type key.
277
325
  /// @return out Cursor scoped to the full block.
278
326
  function take(Cur memory cur, bytes4 key) internal pure returns (Cur memory out) {
279
- (, uint next) = expect(cur, cur.i, key, 0, 0);
327
+ (, uint next) = expect(cur, cur.i, 0, key, 0, 0);
280
328
  out = cur.slice(cur.i, next);
281
329
  cur.i = next;
282
330
  }
283
331
 
332
+ /// @notice Return whether the remaining cursor region is empty or exactly one block with `key`.
333
+ /// Returns false for an empty remaining region. Reverts if the next block has another key or
334
+ /// if a matching block is followed by trailing bytes.
335
+ /// @param cur Source cursor.
336
+ /// @param key Expected optional block key.
337
+ /// @return Whether the remaining region contains exactly one block with `key`.
338
+ function maybeOnly(Cur memory cur, bytes4 key) internal pure returns (bool) {
339
+ if (cur.i == cur.len) return false;
340
+ if (!cur.isAt(key)) revert InvalidBlock();
341
+ if (cur.past() != cur.len) revert IncompleteCursor();
342
+ return true;
343
+ }
344
+
284
345
  /// @notice Consume an optional block with the given key and return a cursor over the full block slice.
285
346
  /// If the current block key does not match, returns an empty cursor and leaves `cur.i` unchanged.
286
347
  /// Otherwise behaves like `take(cur, key)`.
@@ -291,13 +352,13 @@ library Cursors {
291
352
  return cur.isAt(key) ? take(cur, key) : cur.slice(cur.i, cur.i);
292
353
  }
293
354
 
294
- /// @notice Consume an optional ROUTE block at the current position and return a cursor over the full block slice.
295
- /// If the current block is not ROUTE, returns an empty cursor and leaves `cur.i` unchanged.
296
- /// Otherwise behaves like `take(cur, Keys.Route)`.
297
- /// @param cur Cursor positioned at an optional ROUTE block.
298
- /// @return out Cursor scoped to the full ROUTE block, or empty when no ROUTE block is present.
299
- function maybeRoute(Cur memory cur) internal pure returns (Cur memory out) {
300
- return maybeTake(cur, Keys.Route);
355
+ /// @notice Consume an optional DATA block at the current position and return a cursor over the full block slice.
356
+ /// If the current block is not DATA, returns an empty cursor and leaves `cur.i` unchanged.
357
+ /// Otherwise behaves like `take(cur, Keys.Data)`.
358
+ /// @param cur Cursor positioned at an optional DATA block.
359
+ /// @return out Cursor scoped to the full DATA block, or empty when no DATA block is present.
360
+ function maybeData(Cur memory cur) internal pure returns (Cur memory out) {
361
+ return maybeTake(cur, Keys.Data);
301
362
  }
302
363
 
303
364
  /// @notice Enter a List block, prime its member run, and return the group count.
@@ -307,56 +368,45 @@ library Cursors {
307
368
  /// @return next Byte offset immediately after the list payload.
308
369
  function list(Cur memory cur, uint group) internal pure returns (uint groups, uint next) {
309
370
  next = list(cur);
310
- (, , groups) = cur.primeRun(group);
371
+ (, groups) = cur.primeRun(group);
311
372
  if (cur.bound != next) revert IncompleteCursor();
312
373
  }
313
374
 
375
+ /// @notice Exit a nested region at an exact boundary.
376
+ /// Reverts with `IncompleteCursor` if `end` exceeds the cursor region length
377
+ /// or `cur.i != end`.
378
+ /// @param cur Cursor to check.
379
+ /// @param end Relative end offset of the nested region.
380
+ function exit(Cur memory cur, uint end) internal pure {
381
+ if (end > cur.len || cur.i != end) revert IncompleteCursor();
382
+ }
383
+
314
384
  /// @notice Assert that the cursor has consumed exactly up to `bound`.
315
385
  /// Reverts with `IncompleteCursor` if `bound` is zero or `cur.i != cur.bound`.
316
386
  /// @param cur Cursor to check.
317
- function complete(Cur memory cur) internal pure {
387
+ function close(Cur memory cur) internal pure {
318
388
  if (cur.bound == 0 || cur.i != cur.bound) revert IncompleteCursor();
319
389
  }
320
390
 
321
391
  /// @notice Assert that the cursor has consumed its entire source region.
322
392
  /// Reverts with `IncompleteCursor` when `cur.i != cur.len`.
323
393
  /// @param cur Cursor to check.
324
- function end(Cur memory cur) internal pure {
394
+ function complete(Cur memory cur) internal pure {
325
395
  if (cur.i != cur.len) revert IncompleteCursor();
326
396
  }
327
397
 
328
- /// @notice Resume parsing after a nested region delimited by `resumeAt`.
329
- /// Reverts with `IncompleteCursor` if `cur.i` has advanced past `resumeAt` or `resumeAt`
330
- /// exceeds the cursor region length. Otherwise moves `cur.i` to `end`.
331
- /// @param cur Cursor to advance.
332
- /// @param resumeAt Relative end offset of the nested region to resume after.
333
- function resume(Cur memory cur, uint resumeAt) internal pure {
334
- if (resumeAt > cur.len || cur.i > resumeAt) revert IncompleteCursor();
335
- cur.i = resumeAt;
336
- }
337
-
338
- /// @notice Ensure that parsing has reached an exact nested-region boundary.
339
- /// Reverts with `IncompleteCursor` if `ensureAt` exceeds the cursor region length
340
- /// or `cur.i != ensureAt`.
341
- /// @param cur Cursor to check.
342
- /// @param ensureAt Relative offset that `cur.i` must match exactly.
343
- function ensure(Cur memory cur, uint ensureAt) internal pure {
344
- if (ensureAt > cur.len || cur.i != ensureAt) revert IncompleteCursor();
345
- }
346
-
347
- /// @notice Assert completion and finalise a writer in one step.
348
- /// @param cur Cursor to check.
349
- /// @param writer Writer to finalise.
350
- /// @return Trimmed output bytes from the writer.
351
- function complete(Cur memory cur, Writer memory writer) internal pure returns (bytes memory) {
352
- if (cur.bound == 0 || cur.i != cur.bound) revert IncompleteCursor();
353
- return Writers.finish(writer);
354
- }
355
-
356
398
  // -------------------------------------------------------------------------
357
399
  // Block factory helpers
358
400
  // -------------------------------------------------------------------------
359
401
 
402
+ /// @notice Encode a block with a raw payload.
403
+ /// @param key Block type key.
404
+ /// @param data Raw payload bytes.
405
+ /// @return Encoded block bytes.
406
+ function createBlock(bytes4 key, bytes memory data) internal pure returns (bytes memory) {
407
+ return bytes.concat(key, bytes4(uint32(data.length)), data);
408
+ }
409
+
360
410
  /// @notice Encode a block with a single 32-byte payload word.
361
411
  /// @param key Block type key.
362
412
  /// @param value 32-byte payload.
@@ -420,28 +470,11 @@ library Cursors {
420
470
  return bytes.concat(key, bytes4(uint32(0xa0)), a, b, c, d, e);
421
471
  }
422
472
 
423
- /// @notice Encode a block with a 32-byte fixed head followed by a variable-length tail.
424
- /// @param key Block type key.
425
- /// @param head Fixed 32-byte head payload.
426
- /// @param tail Variable-length payload bytes appended after the head.
427
- /// @return Encoded block bytes.
428
- function createBlockHead32(bytes4 key, bytes32 head, bytes memory tail) internal pure returns (bytes memory) {
429
- return bytes.concat(key, bytes4(uint32(0x20 + tail.length)), head, tail);
430
- }
431
-
432
- /// @notice Encode a block with a 64-byte fixed head followed by a variable-length tail.
433
- /// @param key Block type key.
434
- /// @param a First fixed payload word.
435
- /// @param b Second fixed payload word.
436
- /// @param tail Variable-length payload bytes appended after the fixed head.
437
- /// @return Encoded block bytes.
438
- function createBlockHead64(
439
- bytes4 key,
440
- bytes32 a,
441
- bytes32 b,
442
- bytes memory tail
443
- ) internal pure returns (bytes memory) {
444
- return bytes.concat(key, bytes4(uint32(0x40 + tail.length)), a, b, tail);
473
+ /// @notice Encode a BYTES block with a raw payload.
474
+ /// @param data Raw payload bytes.
475
+ /// @return Encoded BYTES block bytes.
476
+ function toBytesBlock(bytes memory data) internal pure returns (bytes memory) {
477
+ return createBlock(Keys.Bytes, data);
445
478
  }
446
479
 
447
480
  /// @notice Encode a BOUNTY block.
@@ -452,13 +485,32 @@ library Cursors {
452
485
  return createBlock64(Keys.Bounty, bytes32(bounty), relayer);
453
486
  }
454
487
 
488
+ /// @notice Encode a BALANCE block.
489
+ /// @param asset Asset identifier.
490
+ /// @param meta Asset metadata slot.
491
+ /// @param amount Token amount.
492
+ /// @return Encoded BALANCE block bytes.
493
+ function toBalanceBlock(bytes32 asset, bytes32 meta, uint amount) internal pure returns (bytes memory) {
494
+ return createBlock96(Keys.Balance, asset, meta, bytes32(amount));
495
+ }
496
+
497
+ /// @notice Encode a CUSTODY block.
498
+ /// @param host Host node ID holding the custody.
499
+ /// @param asset Asset identifier.
500
+ /// @param meta Asset metadata slot.
501
+ /// @param amount Token amount.
502
+ /// @return Encoded CUSTODY block bytes.
503
+ function toCustodyBlock(uint host, bytes32 asset, bytes32 meta, uint amount) internal pure returns (bytes memory) {
504
+ return createBlock128(Keys.Custody, bytes32(host), asset, meta, bytes32(amount));
505
+ }
506
+
455
507
  /// @notice Encode a STEP block.
456
508
  /// @param target Command target identifier.
457
509
  /// @param value Native value forwarded with the step.
458
- /// @param request Variable-length nested request payload.
510
+ /// @param request Raw nested request payload.
459
511
  /// @return Encoded STEP block bytes.
460
512
  function toStepBlock(uint target, uint value, bytes memory request) internal pure returns (bytes memory) {
461
- return createBlockHead64(Keys.Step, bytes32(target), bytes32(value), request);
513
+ return createBlock(Keys.Step, bytes.concat(bytes32(target), bytes32(value), toBytesBlock(request)));
462
514
  }
463
515
 
464
516
  /// @notice Encode a CALL block.
@@ -467,105 +519,113 @@ library Cursors {
467
519
  /// @param data Raw calldata payload for the target.
468
520
  /// @return Encoded CALL block bytes.
469
521
  function toCallBlock(uint target, uint value, bytes memory data) internal pure returns (bytes memory) {
470
- return createBlockHead64(Keys.Call, bytes32(target), bytes32(value), data);
522
+ return createBlock(Keys.Call, bytes.concat(bytes32(target), bytes32(value), toBytesBlock(data)));
471
523
  }
472
524
 
473
- /// @notice Encode a BALANCE block.
474
- /// @param asset Asset identifier.
475
- /// @param meta Asset metadata slot.
476
- /// @param amount Token amount.
477
- /// @return Encoded BALANCE block bytes.
478
- function toBalanceBlock(bytes32 asset, bytes32 meta, uint amount) internal pure returns (bytes memory) {
479
- return createBlock96(Keys.Balance, asset, meta, bytes32(amount));
525
+ /// @notice Encode a CONTEXT block.
526
+ /// @param account Command account identifier.
527
+ /// @param state Embedded state block stream.
528
+ /// @param request Embedded request block stream.
529
+ /// @return Encoded CONTEXT block bytes.
530
+ function toContextBlock(bytes32 account, bytes memory state, bytes memory request) internal pure returns (bytes memory) {
531
+ return createBlock(Keys.Context, bytes.concat(account, toBytesBlock(state), toBytesBlock(request)));
480
532
  }
481
533
 
482
- /// @notice Encode a CUSTODY block.
483
- /// @param host Host node ID holding the custody.
484
- /// @param asset Asset identifier.
485
- /// @param meta Asset metadata slot.
486
- /// @param amount Token amount.
487
- /// @return Encoded CUSTODY block bytes.
488
- function toCustodyBlock(
489
- uint host,
490
- bytes32 asset,
491
- bytes32 meta,
492
- uint amount
534
+ /// @notice Encode a RELAY block.
535
+ /// @param target Command target identifier.
536
+ /// @param value Native value assigned to the relay.
537
+ /// @param account Command account identifier.
538
+ /// @param state Embedded state block stream.
539
+ /// @param request Embedded request block stream.
540
+ /// @return Encoded RELAY block bytes.
541
+ function toRelayBlock(
542
+ uint target,
543
+ uint value,
544
+ bytes32 account,
545
+ bytes memory state,
546
+ bytes memory request
493
547
  ) internal pure returns (bytes memory) {
494
- return createBlock128(Keys.Custody, bytes32(host), asset, meta, bytes32(amount));
548
+ return createBlock(Keys.Relay, bytes.concat(bytes32(target), bytes32(value), toContextBlock(account, state, request)));
495
549
  }
496
550
 
497
551
  // -------------------------------------------------------------------------
498
552
  // Raw calldata loaders
499
553
  // -------------------------------------------------------------------------
500
554
 
501
- /// @notice Load one 32-byte word from calldata.
502
- /// @dev Performs no bounds, key, length, or cursor checks.
503
- /// @param abs Absolute calldata offset of the word start.
504
- /// @return a Loaded word.
505
- function load32(uint abs) internal pure returns (bytes32 a) {
555
+ /// @notice Read the next calldata word from the cursor and advance by `n` bytes.
556
+ /// @dev Performs no bounds, key, length, or cursor checks. Always loads 32 bytes;
557
+ /// callers may cast the returned word to `bytesN` when `n < 32`.
558
+ /// @param cur Cursor whose current position is advanced by `n` bytes.
559
+ /// @param n Number of bytes to advance.
560
+ /// @return value Loaded word.
561
+ function read(Cur memory cur, uint n) internal pure returns (bytes32 value) {
562
+ uint abs = cur.offset + cur.i;
506
563
  assembly ("memory-safe") {
507
- a := calldataload(abs)
564
+ value := calldataload(abs)
508
565
  }
566
+ cur.i += n;
509
567
  }
510
568
 
511
- /// @notice Load two 32-byte words from calldata.
569
+ /// @notice Read the next 16 bytes from the cursor and advance by 16 bytes.
512
570
  /// @dev Performs no bounds, key, length, or cursor checks.
513
- /// @param abs Absolute calldata offset of the first word.
514
- /// @return a First loaded word.
515
- /// @return b Second loaded word.
516
- function load64(uint abs) internal pure returns (bytes32 a, bytes32 b) {
571
+ /// @param cur Cursor whose current position is advanced by 16 bytes.
572
+ /// @return value Loaded bytes16 value.
573
+ function read16(Cur memory cur) internal pure returns (bytes16 value) {
574
+ uint abs = cur.offset + cur.i;
517
575
  assembly ("memory-safe") {
518
- a := calldataload(abs)
519
- b := calldataload(add(abs, 0x20))
576
+ value := calldataload(abs)
520
577
  }
578
+ cur.i += 16;
521
579
  }
522
580
 
523
- /// @notice Load three 32-byte words from calldata.
581
+ /// @notice Read the next 32-byte word from the cursor and advance by one word.
524
582
  /// @dev Performs no bounds, key, length, or cursor checks.
525
- /// @param abs Absolute calldata offset of the first word.
526
- /// @return a First loaded word.
527
- /// @return b Second loaded word.
528
- /// @return c Third loaded word.
529
- function load96(uint abs) internal pure returns (bytes32 a, bytes32 b, bytes32 c) {
583
+ /// @param cur Cursor whose current position is advanced by 32 bytes.
584
+ /// @return value Loaded word.
585
+ function read32(Cur memory cur) internal pure returns (bytes32 value) {
586
+ uint abs = cur.offset + cur.i;
530
587
  assembly ("memory-safe") {
531
- a := calldataload(abs)
532
- b := calldataload(add(abs, 0x20))
533
- c := calldataload(add(abs, 0x40))
588
+ value := calldataload(abs)
534
589
  }
590
+ cur.i += 32;
535
591
  }
536
592
 
537
- /// @notice Load four 32-byte words from calldata.
593
+ /// @notice Read the next two 32-byte words from the cursor and advance by 64 bytes.
538
594
  /// @dev Performs no bounds, key, length, or cursor checks.
539
- /// @param abs Absolute calldata offset of the first word.
595
+ /// @param cur Cursor whose current position is advanced by 64 bytes.
540
596
  /// @return a First loaded word.
541
597
  /// @return b Second loaded word.
542
- /// @return c Third loaded word.
543
- /// @return d Fourth loaded word.
544
- function load128(uint abs) internal pure returns (bytes32 a, bytes32 b, bytes32 c, bytes32 d) {
598
+ function read64(Cur memory cur) internal pure returns (bytes32 a, bytes32 b) {
599
+ uint abs = cur.offset + cur.i;
545
600
  assembly ("memory-safe") {
546
601
  a := calldataload(abs)
547
602
  b := calldataload(add(abs, 0x20))
548
- c := calldataload(add(abs, 0x40))
549
- d := calldataload(add(abs, 0x60))
550
603
  }
604
+ cur.i += 64;
551
605
  }
552
606
 
553
- /// @notice Load five 32-byte words from calldata.
607
+ /// @notice Read the next three 32-byte words from the cursor and advance by 96 bytes.
554
608
  /// @dev Performs no bounds, key, length, or cursor checks.
555
- /// @param abs Absolute calldata offset of the first word.
609
+ /// @param cur Cursor whose current position is advanced by 96 bytes.
556
610
  /// @return a First loaded word.
557
611
  /// @return b Second loaded word.
558
612
  /// @return c Third loaded word.
559
- /// @return d Fourth loaded word.
560
- /// @return e Fifth loaded word.
561
- function load160(uint abs) internal pure returns (bytes32 a, bytes32 b, bytes32 c, bytes32 d, bytes32 e) {
613
+ function read96(Cur memory cur) internal pure returns (bytes32 a, bytes32 b, bytes32 c) {
614
+ uint abs = cur.offset + cur.i;
562
615
  assembly ("memory-safe") {
563
616
  a := calldataload(abs)
564
617
  b := calldataload(add(abs, 0x20))
565
618
  c := calldataload(add(abs, 0x40))
566
- d := calldataload(add(abs, 0x60))
567
- e := calldataload(add(abs, 0x80))
568
619
  }
620
+ cur.i += 96;
621
+ }
622
+
623
+ /// @notice Consume the next 32-byte word and require it to match `expected`.
624
+ /// @dev Performs no bounds, key, length, or cursor checks beyond the value comparison.
625
+ /// @param cur Cursor whose current position is advanced by 32 bytes.
626
+ /// @param expected Required word value.
627
+ function require32(Cur memory cur, bytes32 expected) internal pure {
628
+ if (read32(cur) != expected) revert UnexpectedValue();
569
629
  }
570
630
 
571
631
  // -------------------------------------------------------------------------
@@ -580,17 +640,24 @@ library Cursors {
580
640
  /// @param key Expected dynamic block key.
581
641
  /// @return data Raw block payload bytes.
582
642
  function unpackRaw(Cur memory cur, bytes4 key) internal pure returns (bytes calldata data) {
583
- (uint abs, uint next) = expect(cur, cur.i, key, 0, 0);
643
+ (uint abs, uint next) = expect(cur, cur.i, 0, key, 0, 0);
584
644
  data = msg.data[abs:cur.offset + next];
585
645
  cur.i = next;
586
646
  }
587
647
 
648
+ /// @notice Consume a reserved BYTES block and return its raw payload.
649
+ /// @param cur Cursor; advanced past the BYTES block.
650
+ /// @return data Raw BYTES payload.
651
+ function unpackBytes(Cur memory cur) internal pure returns (bytes calldata data) {
652
+ return unpackRaw(cur, Keys.Bytes);
653
+ }
654
+
588
655
  /// @notice Consume a dynamic block with a single bytes32 payload.
589
656
  /// @param cur Cursor; advanced past the block.
590
657
  /// @param key Expected dynamic block key.
591
658
  /// @return value Decoded bytes32.
592
659
  function unpack32(Cur memory cur, bytes4 key) internal pure returns (bytes32 value) {
593
- uint abs = consume(cur, key, 32, 32);
660
+ uint abs = consume(cur, 0, key, 32, 32);
594
661
  value = bytes32(msg.data[abs:abs + 32]);
595
662
  }
596
663
 
@@ -600,7 +667,7 @@ library Cursors {
600
667
  /// @return a First decoded bytes32.
601
668
  /// @return b Second decoded bytes32.
602
669
  function unpack64(Cur memory cur, bytes4 key) internal pure returns (bytes32 a, bytes32 b) {
603
- uint abs = consume(cur, key, 64, 64);
670
+ uint abs = consume(cur, 0, key, 64, 64);
604
671
  a = bytes32(msg.data[abs:abs + 32]);
605
672
  b = bytes32(msg.data[abs + 32:abs + 64]);
606
673
  }
@@ -612,7 +679,7 @@ library Cursors {
612
679
  /// @return b Second decoded bytes32.
613
680
  /// @return c Third decoded bytes32.
614
681
  function unpack96(Cur memory cur, bytes4 key) internal pure returns (bytes32 a, bytes32 b, bytes32 c) {
615
- uint abs = consume(cur, key, 96, 96);
682
+ uint abs = consume(cur, 0, key, 96, 96);
616
683
  a = bytes32(msg.data[abs:abs + 32]);
617
684
  b = bytes32(msg.data[abs + 32:abs + 64]);
618
685
  c = bytes32(msg.data[abs + 64:abs + 96]);
@@ -625,11 +692,8 @@ library Cursors {
625
692
  /// @return b Second decoded bytes32.
626
693
  /// @return c Third decoded bytes32.
627
694
  /// @return d Fourth decoded bytes32.
628
- function unpack128(
629
- Cur memory cur,
630
- bytes4 key
631
- ) internal pure returns (bytes32 a, bytes32 b, bytes32 c, bytes32 d) {
632
- uint abs = consume(cur, key, 128, 128);
695
+ function unpack128(Cur memory cur, bytes4 key) internal pure returns (bytes32 a, bytes32 b, bytes32 c, bytes32 d) {
696
+ uint abs = consume(cur, 0, key, 128, 128);
633
697
  a = bytes32(msg.data[abs:abs + 32]);
634
698
  b = bytes32(msg.data[abs + 32:abs + 64]);
635
699
  c = bytes32(msg.data[abs + 64:abs + 96]);
@@ -648,7 +712,7 @@ library Cursors {
648
712
  Cur memory cur,
649
713
  bytes4 key
650
714
  ) internal pure returns (bytes32 a, bytes32 b, bytes32 c, bytes32 d, bytes32 e) {
651
- uint abs = consume(cur, key, 160, 160);
715
+ uint abs = consume(cur, 0, key, 160, 160);
652
716
  a = bytes32(msg.data[abs:abs + 32]);
653
717
  b = bytes32(msg.data[abs + 32:abs + 64]);
654
718
  c = bytes32(msg.data[abs + 64:abs + 96]);
@@ -656,102 +720,6 @@ library Cursors {
656
720
  e = bytes32(msg.data[abs + 128:abs + 160]);
657
721
  }
658
722
 
659
- /// @notice Consume a dynamic block with a single uint payload.
660
- /// @param cur Cursor; advanced past the block.
661
- /// @param key Expected dynamic block key.
662
- /// @return value Decoded uint value.
663
- function unpackUint(Cur memory cur, bytes4 key) internal pure returns (uint value) {
664
- value = uint(unpack32(cur, key));
665
- }
666
-
667
- /// @notice Consume a dynamic block with two uint payload words.
668
- /// @param cur Cursor; advanced past the block.
669
- /// @param key Expected dynamic block key.
670
- /// @return a First decoded uint.
671
- /// @return b Second decoded uint.
672
- function unpack2Uint(Cur memory cur, bytes4 key) internal pure returns (uint a, uint b) {
673
- (bytes32 x, bytes32 y) = unpack64(cur, key);
674
- return (uint(x), uint(y));
675
- }
676
-
677
- /// @notice Consume a dynamic block with three uint payload words.
678
- /// @param cur Cursor; advanced past the block.
679
- /// @param key Expected dynamic block key.
680
- /// @return a First decoded uint.
681
- /// @return b Second decoded uint.
682
- /// @return c Third decoded uint.
683
- function unpack3Uint(Cur memory cur, bytes4 key) internal pure returns (uint a, uint b, uint c) {
684
- (bytes32 x, bytes32 y, bytes32 z) = unpack96(cur, key);
685
- return (uint(x), uint(y), uint(z));
686
- }
687
-
688
- // Generic fixed-head decoders
689
-
690
- /// @notice Consume a dynamic block with a 32-byte fixed head followed by a variable-length tail.
691
- /// @param cur Cursor; advanced past the block.
692
- /// @param key Expected dynamic block key.
693
- /// @return head Fixed 32-byte head.
694
- /// @return tail Variable-length payload bytes after the fixed head.
695
- function unpackHead32(Cur memory cur, bytes4 key) internal pure returns (bytes32 head, bytes calldata tail) {
696
- uint abs = consume(cur, key, 32, 0);
697
- head = bytes32(msg.data[abs:abs + 32]);
698
- tail = msg.data[abs + 32:cur.offset + cur.i];
699
- }
700
-
701
- /// @notice Consume a dynamic block with a 64-byte fixed head followed by a variable-length tail.
702
- /// @param cur Cursor; advanced past the block.
703
- /// @param key Expected dynamic block key.
704
- /// @return a First fixed head word.
705
- /// @return b Second fixed head word.
706
- /// @return tail Variable-length payload bytes after the fixed head.
707
- function unpackHead64(
708
- Cur memory cur,
709
- bytes4 key
710
- ) internal pure returns (bytes32 a, bytes32 b, bytes calldata tail) {
711
- uint abs = consume(cur, key, 64, 0);
712
- a = bytes32(msg.data[abs:abs + 32]);
713
- b = bytes32(msg.data[abs + 32:abs + 64]);
714
- tail = msg.data[abs + 64:cur.offset + cur.i];
715
- }
716
-
717
- /// @notice Consume a dynamic block with a 96-byte fixed head followed by a variable-length tail.
718
- /// @param cur Cursor; advanced past the block.
719
- /// @param key Expected dynamic block key.
720
- /// @return a First fixed head word.
721
- /// @return b Second fixed head word.
722
- /// @return c Third fixed head word.
723
- /// @return tail Variable-length payload bytes after the fixed head.
724
- function unpackHead96(
725
- Cur memory cur,
726
- bytes4 key
727
- ) internal pure returns (bytes32 a, bytes32 b, bytes32 c, bytes calldata tail) {
728
- uint abs = consume(cur, key, 96, 0);
729
- a = bytes32(msg.data[abs:abs + 32]);
730
- b = bytes32(msg.data[abs + 32:abs + 64]);
731
- c = bytes32(msg.data[abs + 64:abs + 96]);
732
- tail = msg.data[abs + 96:cur.offset + cur.i];
733
- }
734
-
735
- /// @notice Consume a dynamic block with a 128-byte fixed head followed by a variable-length tail.
736
- /// @param cur Cursor; advanced past the block.
737
- /// @param key Expected dynamic block key.
738
- /// @return a First fixed head word.
739
- /// @return b Second fixed head word.
740
- /// @return c Third fixed head word.
741
- /// @return d Fourth fixed head word.
742
- /// @return tail Variable-length payload bytes after the fixed head.
743
- function unpackHead128(
744
- Cur memory cur,
745
- bytes4 key
746
- ) internal pure returns (bytes32 a, bytes32 b, bytes32 c, bytes32 d, bytes calldata tail) {
747
- uint abs = consume(cur, key, 128, 0);
748
- a = bytes32(msg.data[abs:abs + 32]);
749
- b = bytes32(msg.data[abs + 32:abs + 64]);
750
- c = bytes32(msg.data[abs + 64:abs + 96]);
751
- d = bytes32(msg.data[abs + 96:abs + 128]);
752
- tail = msg.data[abs + 128:cur.offset + cur.i];
753
- }
754
-
755
723
  // Generic typed-shape decoders
756
724
 
757
725
  /// @notice Consume a fixed-size asset amount block and return asset, meta, and amount.
@@ -764,7 +732,7 @@ library Cursors {
764
732
  Cur memory cur,
765
733
  bytes4 key
766
734
  ) internal pure returns (bytes32 asset, bytes32 meta, uint amount) {
767
- uint abs = consume(cur, key, 96, 96);
735
+ uint abs = consume(cur, 0, key, 96, 96);
768
736
  asset = bytes32(msg.data[abs:abs + 32]);
769
737
  meta = bytes32(msg.data[abs + 32:abs + 64]);
770
738
  amount = uint(bytes32(msg.data[abs + 64:abs + 96]));
@@ -781,7 +749,7 @@ library Cursors {
781
749
  Cur memory cur,
782
750
  bytes4 key
783
751
  ) internal pure returns (bytes32 account, bytes32 asset, bytes32 meta, uint amount) {
784
- uint abs = consume(cur, key, 128, 128);
752
+ uint abs = consume(cur, 0, key, 128, 128);
785
753
  account = bytes32(msg.data[abs:abs + 32]);
786
754
  asset = bytes32(msg.data[abs + 32:abs + 64]);
787
755
  meta = bytes32(msg.data[abs + 64:abs + 96]);
@@ -799,7 +767,7 @@ library Cursors {
799
767
  Cur memory cur,
800
768
  bytes4 key
801
769
  ) internal pure returns (uint host, bytes32 asset, bytes32 meta, uint amount) {
802
- uint abs = consume(cur, key, 128, 128);
770
+ uint abs = consume(cur, 0, key, 128, 128);
803
771
  host = uint(bytes32(msg.data[abs:abs + 32]));
804
772
  asset = bytes32(msg.data[abs + 32:abs + 64]);
805
773
  meta = bytes32(msg.data[abs + 64:abs + 96]);
@@ -817,7 +785,7 @@ library Cursors {
817
785
  Cur memory cur,
818
786
  bytes4 key
819
787
  ) internal pure returns (uint host, bytes32 account, bytes32 asset, bytes32 meta) {
820
- uint abs = consume(cur, key, 128, 128);
788
+ uint abs = consume(cur, 0, key, 128, 128);
821
789
  host = uint(bytes32(msg.data[abs:abs + 32]));
822
790
  account = bytes32(msg.data[abs + 32:abs + 64]);
823
791
  asset = bytes32(msg.data[abs + 64:abs + 96]);
@@ -836,7 +804,7 @@ library Cursors {
836
804
  Cur memory cur,
837
805
  bytes4 key
838
806
  ) internal pure returns (bytes32 from, bytes32 to, bytes32 asset, bytes32 meta, uint amount) {
839
- uint abs = consume(cur, key, 160, 160);
807
+ uint abs = consume(cur, 0, key, 160, 160);
840
808
  from = bytes32(msg.data[abs:abs + 32]);
841
809
  to = bytes32(msg.data[abs + 32:abs + 64]);
842
810
  asset = bytes32(msg.data[abs + 64:abs + 96]);
@@ -860,20 +828,6 @@ library Cursors {
860
828
  node = uint(unpack32(cur, Keys.Node));
861
829
  }
862
830
 
863
- /// @notice Consume a RATE block and return the value.
864
- /// @param cur Cursor; advanced past the block.
865
- /// @return value Encoded ratio or rate.
866
- function unpackRate(Cur memory cur) internal pure returns (uint value) {
867
- value = uint(unpack32(cur, Keys.Rate));
868
- }
869
-
870
- /// @notice Consume a QUANTITY block and return the amount.
871
- /// @param cur Cursor; advanced past the block.
872
- /// @return amount Scalar quantity value.
873
- function unpackQuantity(Cur memory cur) internal pure returns (uint amount) {
874
- amount = uint(unpack32(cur, Keys.Quantity));
875
- }
876
-
877
831
  /// @notice Consume a FEE block and return the amount.
878
832
  /// @param cur Cursor; advanced past the block.
879
833
  /// @return amount Fee amount.
@@ -895,7 +849,7 @@ library Cursors {
895
849
  /// @return asset Asset identifier.
896
850
  /// @return meta Asset metadata slot.
897
851
  function unpackAccountAsset(Cur memory cur) internal pure returns (bytes32 account, bytes32 asset, bytes32 meta) {
898
- uint abs = consume(cur, Keys.AccountAsset, 96, 96);
852
+ uint abs = consume(cur, 0, Keys.AccountAsset, 96, 96);
899
853
  account = bytes32(msg.data[abs:abs + 32]);
900
854
  asset = bytes32(msg.data[abs + 32:abs + 64]);
901
855
  meta = bytes32(msg.data[abs + 64:abs + 96]);
@@ -918,18 +872,6 @@ library Cursors {
918
872
  relayer = y;
919
873
  }
920
874
 
921
- /// @notice Consume a BOUNDS block and return the signed min and max values.
922
- /// @param cur Cursor; advanced past the block.
923
- /// @return min Lower signed bound.
924
- /// @return max Upper signed bound.
925
- function unpackBounds(Cur memory cur) internal pure returns (int min, int max) {
926
- uint abs = consume(cur, Keys.Bounds, 64, 64);
927
- assembly ("memory-safe") {
928
- min := calldataload(abs)
929
- max := calldataload(add(abs, 0x20))
930
- }
931
- }
932
-
933
875
  /// @notice Consume an AMOUNT block and return its fields as separate values.
934
876
  /// @param cur Cursor; advanced past the block.
935
877
  /// @return asset Asset identifier.
@@ -1095,9 +1037,7 @@ library Cursors {
1095
1037
  /// @return asset Asset identifier.
1096
1038
  /// @return meta Asset metadata slot.
1097
1039
  /// @return amount Token amount.
1098
- function unpackCustody(
1099
- Cur memory cur
1100
- ) internal pure returns (uint host, bytes32 asset, bytes32 meta, uint amount) {
1040
+ function unpackCustody(Cur memory cur) internal pure returns (uint host, bytes32 asset, bytes32 meta, uint amount) {
1101
1041
  return unpackHostAmount(cur, Keys.Custody);
1102
1042
  }
1103
1043
 
@@ -1131,25 +1071,62 @@ library Cursors {
1131
1071
  // Type-specific dynamic decoders
1132
1072
 
1133
1073
  /// @notice Consume a STEP block and return its sub-command invocation fields.
1134
- /// The `req` slice covers any additional payload bytes after the fixed head.
1074
+ /// The `req` slice is the raw payload of the block's required BYTES child.
1135
1075
  /// @param cur Cursor; advanced past the block.
1136
1076
  /// @return target Destination node ID for the sub-command.
1137
1077
  /// @return value Native value to forward with the call.
1138
1078
  /// @return req Embedded request bytes for the sub-command.
1139
1079
  function unpackStep(Cur memory cur) internal pure returns (uint target, uint value, bytes calldata req) {
1140
- (bytes32 a, bytes32 b, bytes calldata tail) = unpackHead64(cur, Keys.Step);
1141
- return (uint(a), uint(b), tail);
1080
+ uint end = cur.enter(Keys.Step, 64 + Sizes.Header, 0);
1081
+ target = uint(cur.read32());
1082
+ value = uint(cur.read32());
1083
+ req = cur.unpackBytes();
1084
+ cur.exit(end);
1142
1085
  }
1143
1086
 
1144
1087
  /// @notice Consume a CALL block and return its target invocation fields.
1145
- /// The `data` slice covers any additional payload bytes after the fixed head.
1088
+ /// The `data` slice is the raw payload of the block's required BYTES child.
1146
1089
  /// @param cur Cursor; advanced past the block.
1147
1090
  /// @return target Target node ID to call.
1148
1091
  /// @return value Native value to forward with the call.
1149
1092
  /// @return data Raw calldata payload for the target.
1150
1093
  function unpackCall(Cur memory cur) internal pure returns (uint target, uint value, bytes calldata data) {
1151
- (bytes32 a, bytes32 b, bytes calldata tail) = unpackHead64(cur, Keys.Call);
1152
- return (uint(a), uint(b), tail);
1094
+ uint end = cur.enter(Keys.Call, 64 + Sizes.Header, 0);
1095
+ target = uint(cur.read32());
1096
+ value = uint(cur.read32());
1097
+ data = cur.unpackBytes();
1098
+ cur.exit(end);
1099
+ }
1100
+
1101
+ /// @notice Consume a CONTEXT block and return its command context fields.
1102
+ /// The `state` and `request` slices are the raw payloads of the required BYTES children.
1103
+ /// @param cur Cursor; advanced past the block.
1104
+ /// @return account Command account identifier.
1105
+ /// @return state Embedded state block stream.
1106
+ /// @return request Embedded request block stream.
1107
+ function unpackContext(Cur memory cur) internal pure returns (bytes32 account, bytes calldata state, bytes calldata request) {
1108
+ uint end = cur.enter(Keys.Context, 32 + 2 * Sizes.Header, 0);
1109
+ account = cur.read32();
1110
+ state = cur.unpackBytes();
1111
+ request = cur.unpackBytes();
1112
+ cur.exit(end);
1113
+ }
1114
+
1115
+ /// @notice Consume a RELAY block and return its target, value, and context fields.
1116
+ /// @param cur Cursor; advanced past the block.
1117
+ /// @return target Destination command/node ID.
1118
+ /// @return value Native value assigned to the relay.
1119
+ /// @return account Command account identifier.
1120
+ /// @return state Embedded state block stream.
1121
+ /// @return request Embedded request block stream.
1122
+ function unpackRelay(
1123
+ Cur memory cur
1124
+ ) internal pure returns (uint target, uint value, bytes32 account, bytes calldata state, bytes calldata request) {
1125
+ uint end = cur.enter(Keys.Relay, 64 + Sizes.Header + 32 + 2 * Sizes.Header, 0);
1126
+ target = uint(cur.read32());
1127
+ value = uint(cur.read32());
1128
+ (account, state, request) = cur.unpackContext();
1129
+ cur.exit(end);
1153
1130
  }
1154
1131
 
1155
1132
  // Type-specific validators
@@ -1162,10 +1139,12 @@ library Cursors {
1162
1139
  /// @return deadline Expiry timestamp.
1163
1140
  /// @return proof Raw proof bytes (layout: `[bytes20 signer][bytes65 sig]`).
1164
1141
  function expectAuth(Cur memory cur, uint i, uint cid) internal pure returns (uint deadline, bytes calldata proof) {
1165
- (uint abs, uint next) = expect(cur, i, Keys.Auth, 149, 0);
1142
+ (uint abs, uint next) = expect(cur, i, 0, Keys.Auth, 64 + Sizes.Header + Sizes.Proof, 0);
1166
1143
  if (uint(bytes32(msg.data[abs:abs + 32])) != cid) revert UnexpectedValue();
1167
1144
  deadline = uint(bytes32(msg.data[abs + 32:abs + 64]));
1168
- proof = msg.data[abs + 64:cur.offset + next];
1145
+
1146
+ (abs, ) = expect(cur, i + Sizes.Header + 64, next, Keys.Bytes, Sizes.Proof, Sizes.Proof);
1147
+ proof = msg.data[abs:abs + Sizes.Proof];
1169
1148
  }
1170
1149
 
1171
1150
  // -------------------------------------------------------------------------
@@ -1183,7 +1162,7 @@ library Cursors {
1183
1162
  bytes4 key,
1184
1163
  bytes32 asset
1185
1164
  ) internal pure returns (bytes32 meta, uint amount) {
1186
- uint abs = consume(cur, key, 96, 96);
1165
+ uint abs = consume(cur, 0, key, 96, 96);
1187
1166
  if (bytes32(msg.data[abs:abs + 32]) != asset) revert UnexpectedValue();
1188
1167
  meta = bytes32(msg.data[abs + 32:abs + 64]);
1189
1168
  amount = uint(bytes32(msg.data[abs + 64:abs + 96]));
@@ -1201,7 +1180,7 @@ library Cursors {
1201
1180
  bytes32 asset,
1202
1181
  bytes32 meta
1203
1182
  ) internal pure returns (uint amount) {
1204
- uint abs = consume(cur, key, 96, 96);
1183
+ uint abs = consume(cur, 0, key, 96, 96);
1205
1184
  if (bytes32(msg.data[abs:abs + 32]) != asset) revert UnexpectedValue();
1206
1185
  if (bytes32(msg.data[abs + 32:abs + 64]) != meta) revert UnexpectedValue();
1207
1186
  amount = uint(bytes32(msg.data[abs + 64:abs + 96]));
@@ -1213,7 +1192,7 @@ library Cursors {
1213
1192
  /// @param asset Expected asset identifier.
1214
1193
  /// @return meta Metadata slot from the block.
1215
1194
  function requireUnitAssetAmount(Cur memory cur, bytes4 key, bytes32 asset) internal pure returns (bytes32 meta) {
1216
- uint abs = consume(cur, key, 96, 96);
1195
+ uint abs = consume(cur, 0, key, 96, 96);
1217
1196
  if (bytes32(msg.data[abs:abs + 32]) != asset) revert UnexpectedValue();
1218
1197
  meta = bytes32(msg.data[abs + 32:abs + 64]);
1219
1198
  if (uint(bytes32(msg.data[abs + 64:abs + 96])) != 1) revert UnexpectedValue();
@@ -1240,7 +1219,7 @@ library Cursors {
1240
1219
  bytes4 key,
1241
1220
  uint host
1242
1221
  ) internal pure returns (bytes32 asset, bytes32 meta, uint amount) {
1243
- uint abs = consume(cur, key, 128, 128);
1222
+ uint abs = consume(cur, 0, key, 128, 128);
1244
1223
  if (uint(bytes32(msg.data[abs:abs + 32])) != host) revert UnexpectedValue();
1245
1224
  asset = bytes32(msg.data[abs + 32:abs + 64]);
1246
1225
  meta = bytes32(msg.data[abs + 64:abs + 96]);
@@ -1260,7 +1239,7 @@ library Cursors {
1260
1239
  uint host,
1261
1240
  bytes32 asset
1262
1241
  ) internal pure returns (bytes32 meta, uint amount) {
1263
- uint abs = consume(cur, key, 128, 128);
1242
+ uint abs = consume(cur, 0, key, 128, 128);
1264
1243
  if (uint(bytes32(msg.data[abs:abs + 32])) != host) revert UnexpectedValue();
1265
1244
  if (bytes32(msg.data[abs + 32:abs + 64]) != asset) revert UnexpectedValue();
1266
1245
  meta = bytes32(msg.data[abs + 64:abs + 96]);
@@ -1279,7 +1258,7 @@ library Cursors {
1279
1258
  uint host,
1280
1259
  bytes32 asset
1281
1260
  ) internal pure returns (bytes32 meta) {
1282
- uint abs = consume(cur, key, 128, 128);
1261
+ uint abs = consume(cur, 0, key, 128, 128);
1283
1262
  if (uint(bytes32(msg.data[abs:abs + 32])) != host) revert UnexpectedValue();
1284
1263
  if (bytes32(msg.data[abs + 32:abs + 64]) != asset) revert UnexpectedValue();
1285
1264
  meta = bytes32(msg.data[abs + 64:abs + 96]);
@@ -1299,7 +1278,7 @@ library Cursors {
1299
1278
  uint host,
1300
1279
  bytes32 account
1301
1280
  ) internal pure returns (bytes32 asset, bytes32 meta) {
1302
- uint abs = consume(cur, key, 128, 128);
1281
+ uint abs = consume(cur, 0, key, 128, 128);
1303
1282
  if (uint(bytes32(msg.data[abs:abs + 32])) != host) revert UnexpectedValue();
1304
1283
  if (bytes32(msg.data[abs + 32:abs + 64]) != account) revert UnexpectedValue();
1305
1284
  asset = bytes32(msg.data[abs + 64:abs + 96]);
@@ -1318,7 +1297,7 @@ library Cursors {
1318
1297
  bytes4 key,
1319
1298
  uint host
1320
1299
  ) internal pure returns (bytes32 account, bytes32 asset, bytes32 meta) {
1321
- uint abs = consume(cur, key, 128, 128);
1300
+ uint abs = consume(cur, 0, key, 128, 128);
1322
1301
  if (uint(bytes32(msg.data[abs:abs + 32])) != host) revert UnexpectedValue();
1323
1302
  account = bytes32(msg.data[abs + 32:abs + 64]);
1324
1303
  asset = bytes32(msg.data[abs + 64:abs + 96]);
@@ -1345,7 +1324,7 @@ library Cursors {
1345
1324
  /// @return proof Raw proof bytes.
1346
1325
  function requireAuth(Cur memory cur, uint cid) internal pure returns (uint deadline, bytes calldata proof) {
1347
1326
  (deadline, proof) = expectAuth(cur, cur.i, cid);
1348
- cur.i += Sizes.Header + 64 + proof.length;
1327
+ cur.i += Sizes.Auth;
1349
1328
  }
1350
1329
 
1351
1330
  // -------------------------------------------------------------------------
@@ -1362,7 +1341,7 @@ library Cursors {
1362
1341
  uint i = find(cur, 0, Keys.Node);
1363
1342
  if (i == cur.len) return backup;
1364
1343
 
1365
- (uint abs, ) = expect(cur, i, Keys.Node, 32, 32);
1344
+ (uint abs, ) = expect(cur, i, 0, Keys.Node, 32, 32);
1366
1345
  return uint(bytes32(msg.data[abs:abs + 32]));
1367
1346
  }
1368
1347
 
@@ -1386,7 +1365,7 @@ library Cursors {
1386
1365
  uint i = find(cur, 0, Keys.Account);
1387
1366
  if (i == cur.len) return backup;
1388
1367
 
1389
- (uint abs, ) = expect(cur, i, Keys.Account, 32, 32);
1368
+ (uint abs, ) = expect(cur, i, 0, Keys.Account, 32, 32);
1390
1369
  return bytes32(msg.data[abs:abs + 32]);
1391
1370
  }
1392
1371
 
@@ -1399,7 +1378,7 @@ library Cursors {
1399
1378
  uint i = find(cur, cur.bound, Keys.Node);
1400
1379
  if (i == cur.len) return backup;
1401
1380
 
1402
- (uint abs, ) = expect(cur, i, Keys.Node, 32, 32);
1381
+ (uint abs, ) = expect(cur, i, 0, Keys.Node, 32, 32);
1403
1382
  return uint(bytes32(msg.data[abs:abs + 32]));
1404
1383
  }
1405
1384
 
@@ -1412,7 +1391,7 @@ library Cursors {
1412
1391
  uint i = find(cur, cur.bound, Keys.Account);
1413
1392
  if (i == cur.len) return backup;
1414
1393
 
1415
- (uint abs, ) = expect(cur, i, Keys.Account, 32, 32);
1394
+ (uint abs, ) = expect(cur, i, 0, Keys.Account, 32, 32);
1416
1395
  return bytes32(msg.data[abs:abs + 32]);
1417
1396
  }
1418
1397
 
@@ -1422,19 +1401,19 @@ library Cursors {
1422
1401
  /// The signed slice covers from `cur.i` up to (but not including) the AUTH proof bytes.
1423
1402
  /// @param cur Source cursor; `bound` marks the end of the primary data region.
1424
1403
  /// @param cid Command ID that the signature must be bound to.
1425
- /// @return hash keccak256 of the signed message slice.
1404
+ /// @return digest keccak256 of the signed message slice.
1426
1405
  /// @return deadline Expiry timestamp from the AUTH block.
1427
1406
  /// @return proof Raw proof bytes (layout: `[bytes20 signer][bytes65 sig]`).
1428
1407
  function authLast(
1429
1408
  Cur memory cur,
1430
1409
  uint cid
1431
- ) internal pure returns (bytes32 hash, uint deadline, bytes calldata proof) {
1410
+ ) internal pure returns (bytes32 digest, uint deadline, bytes calldata proof) {
1432
1411
  if (cur.len - cur.i < Sizes.Auth) revert MalformedBlocks();
1433
1412
 
1434
1413
  uint i = cur.len - Sizes.Auth;
1435
1414
  if (i < cur.bound) revert MalformedBlocks();
1436
1415
 
1437
1416
  (deadline, proof) = expectAuth(cur, i, cid);
1438
- hash = keccak256(msg.data[cur.offset + cur.i:cur.offset + cur.len - Sizes.Proof]);
1417
+ digest = cur.hash(cur.i, cur.len - Sizes.Proof);
1439
1418
  }
1440
1419
  }