@rootzero/contracts 0.2.0 → 0.4.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 (161) hide show
  1. package/{contracts/Commands.sol → Commands.sol} +7 -2
  2. package/{contracts/Core.sol → Core.sol} +7 -1
  3. package/{contracts/Blocks.sol → Cursors.sol} +8 -1
  4. package/{contracts/Events.sol → Events.sol} +6 -0
  5. package/README.md +59 -59
  6. package/Utils.sol +18 -0
  7. package/blocks/Cursors.sol +800 -0
  8. package/blocks/Keys.sol +51 -0
  9. package/blocks/Mem.sol +188 -0
  10. package/blocks/Schema.sol +158 -0
  11. package/blocks/Writers.sol +304 -0
  12. package/commands/Base.sol +65 -0
  13. package/commands/Borrow.sol +88 -0
  14. package/commands/Burn.sol +45 -0
  15. package/commands/Create.sol +41 -0
  16. package/commands/Credit.sol +49 -0
  17. package/commands/Debit.sol +58 -0
  18. package/commands/Deposit.sol +57 -0
  19. package/commands/Liquidate.sol +98 -0
  20. package/commands/Liquidity.sol +179 -0
  21. package/commands/Mint.sol +53 -0
  22. package/{contracts/commands → commands}/Pipe.sol +28 -12
  23. package/commands/Provision.sol +84 -0
  24. package/commands/Reclaim.sol +54 -0
  25. package/commands/Redeem.sol +98 -0
  26. package/commands/Remove.sol +41 -0
  27. package/commands/Repay.sol +98 -0
  28. package/commands/Settle.sol +41 -0
  29. package/commands/Stake.sol +129 -0
  30. package/commands/Supply.sol +40 -0
  31. package/commands/Swap.sol +89 -0
  32. package/commands/Transfer.sol +55 -0
  33. package/commands/Unstake.sol +57 -0
  34. package/commands/Withdraw.sol +49 -0
  35. package/commands/admin/Allocate.sol +40 -0
  36. package/commands/admin/AllowAssets.sol +42 -0
  37. package/commands/admin/Authorize.sol +38 -0
  38. package/commands/admin/DenyAssets.sol +42 -0
  39. package/commands/admin/Destroy.sol +38 -0
  40. package/commands/admin/Init.sol +38 -0
  41. package/commands/admin/Relocate.sol +39 -0
  42. package/commands/admin/Unauthorize.sol +38 -0
  43. package/core/Access.sol +79 -0
  44. package/core/Balances.sol +40 -0
  45. package/core/Host.sol +44 -0
  46. package/core/Operation.sol +69 -0
  47. package/core/Validator.sol +46 -0
  48. package/docs/GETTING_STARTED.md +286 -0
  49. package/{contracts/events → events}/Access.sol +7 -0
  50. package/events/Asset.sol +21 -0
  51. package/{contracts/events → events}/Balance.sol +10 -0
  52. package/events/Collateral.sol +24 -0
  53. package/events/Command.sol +24 -0
  54. package/events/Debt.sol +25 -0
  55. package/{contracts/events → events}/Deposit.sol +9 -0
  56. package/events/Emitter.sol +14 -0
  57. package/{contracts/events → events}/Governed.sol +7 -0
  58. package/{contracts/events → events}/HostAnnounced.sol +8 -0
  59. package/{contracts/events → events}/Listing.sol +9 -0
  60. package/{contracts/events → events}/Peer.sol +8 -0
  61. package/{contracts/events → events}/Quote.sol +7 -0
  62. package/{contracts/events → events}/RootZero.sol +5 -0
  63. package/{contracts/events → events}/Withdraw.sol +9 -0
  64. package/interfaces/IHostDiscovery.sol +16 -0
  65. package/package.json +17 -33
  66. package/peer/AllowAssets.sol +44 -0
  67. package/peer/Base.sol +25 -0
  68. package/peer/DenyAssets.sol +44 -0
  69. package/peer/Pull.sol +42 -0
  70. package/peer/Push.sol +42 -0
  71. package/utils/Accounts.sol +90 -0
  72. package/utils/Assets.sol +138 -0
  73. package/utils/ECDSA.sol +58 -0
  74. package/utils/Ids.sol +129 -0
  75. package/utils/Layout.sol +66 -0
  76. package/utils/State.sol +22 -0
  77. package/utils/Utils.sol +194 -0
  78. package/utils/Value.sol +32 -0
  79. package/contracts/Utils.sol +0 -12
  80. package/contracts/blocks/Blocks.sol +0 -818
  81. package/contracts/blocks/Keys.sol +0 -24
  82. package/contracts/blocks/Mem.sol +0 -129
  83. package/contracts/blocks/Schema.sol +0 -105
  84. package/contracts/blocks/Writers.sol +0 -209
  85. package/contracts/combinators/AmountToBalance.sol +0 -25
  86. package/contracts/combinators/AmountToCustody.sol +0 -36
  87. package/contracts/combinators/CustodyToBalance.sol +0 -25
  88. package/contracts/combinators/EachRoute.sol +0 -18
  89. package/contracts/combinators/MapBalance.sol +0 -25
  90. package/contracts/combinators/MapCustody.sol +0 -25
  91. package/contracts/combinators/RouteToBalance.sol +0 -27
  92. package/contracts/commands/Base.sol +0 -40
  93. package/contracts/commands/Borrow.sol +0 -89
  94. package/contracts/commands/Burn.sol +0 -33
  95. package/contracts/commands/Create.sol +0 -32
  96. package/contracts/commands/Credit.sol +0 -36
  97. package/contracts/commands/Debit.sol +0 -46
  98. package/contracts/commands/Deposit.sol +0 -45
  99. package/contracts/commands/Liquidate.sol +0 -101
  100. package/contracts/commands/Liquidity.sol +0 -179
  101. package/contracts/commands/Mint.sol +0 -42
  102. package/contracts/commands/Provision.sol +0 -73
  103. package/contracts/commands/Reclaim.sol +0 -48
  104. package/contracts/commands/Redeem.sol +0 -101
  105. package/contracts/commands/Remove.sol +0 -32
  106. package/contracts/commands/Repay.sol +0 -101
  107. package/contracts/commands/Settle.sol +0 -32
  108. package/contracts/commands/Stake.sol +0 -121
  109. package/contracts/commands/Supply.sol +0 -33
  110. package/contracts/commands/Swap.sol +0 -88
  111. package/contracts/commands/Transfer.sol +0 -44
  112. package/contracts/commands/Unstake.sol +0 -49
  113. package/contracts/commands/Withdraw.sol +0 -37
  114. package/contracts/commands/admin/Allocate.sol +0 -32
  115. package/contracts/commands/admin/AllowAssets.sol +0 -34
  116. package/contracts/commands/admin/Authorize.sol +0 -30
  117. package/contracts/commands/admin/DenyAssets.sol +0 -34
  118. package/contracts/commands/admin/Destroy.sol +0 -27
  119. package/contracts/commands/admin/Init.sol +0 -26
  120. package/contracts/commands/admin/Relocate.sol +0 -30
  121. package/contracts/commands/admin/Unauthorize.sol +0 -30
  122. package/contracts/core/Access.sol +0 -50
  123. package/contracts/core/Balances.sol +0 -23
  124. package/contracts/core/Host.sol +0 -25
  125. package/contracts/core/Operation.sol +0 -32
  126. package/contracts/core/Validator.sol +0 -31
  127. package/contracts/events/Asset.sol +0 -14
  128. package/contracts/events/Collateral.sol +0 -15
  129. package/contracts/events/Command.sol +0 -14
  130. package/contracts/events/Debt.sol +0 -15
  131. package/contracts/events/Emitter.sol +0 -7
  132. package/contracts/interfaces/IHostDiscovery.sol +0 -6
  133. package/contracts/peer/AllowAssets.sol +0 -30
  134. package/contracts/peer/Base.sol +0 -17
  135. package/contracts/peer/DenyAssets.sol +0 -30
  136. package/contracts/peer/Pull.sol +0 -30
  137. package/contracts/peer/Push.sol +0 -30
  138. package/contracts/test/TestBlockHelper.sol +0 -261
  139. package/contracts/test/TestBorrowHost.sol +0 -47
  140. package/contracts/test/TestBurnHost.sol +0 -28
  141. package/contracts/test/TestCreateHost.sol +0 -26
  142. package/contracts/test/TestDiscovery.sol +0 -6
  143. package/contracts/test/TestECDSA.sol +0 -16
  144. package/contracts/test/TestHost.sol +0 -199
  145. package/contracts/test/TestLiquidityHost.sol +0 -145
  146. package/contracts/test/TestMintHost.sol +0 -40
  147. package/contracts/test/TestPeerHost.sol +0 -34
  148. package/contracts/test/TestReclaimHost.sol +0 -48
  149. package/contracts/test/TestRejectEther.sol +0 -8
  150. package/contracts/test/TestRemoveHost.sol +0 -26
  151. package/contracts/test/TestSwapHost.sol +0 -44
  152. package/contracts/test/TestUtils.sol +0 -169
  153. package/contracts/test/TestValidator.sol +0 -10
  154. package/contracts/utils/Accounts.sol +0 -40
  155. package/contracts/utils/Assets.sol +0 -76
  156. package/contracts/utils/Channels.sol +0 -11
  157. package/contracts/utils/ECDSA.sol +0 -36
  158. package/contracts/utils/Ids.sol +0 -75
  159. package/contracts/utils/Layout.sol +0 -22
  160. package/contracts/utils/Utils.sol +0 -126
  161. package/contracts/utils/Value.sol +0 -20
@@ -0,0 +1,800 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import {HostAsset, AssetAmount, HostAmount, Tx, Keys, Sizes} from "./Schema.sol";
5
+ import {ALLOC_SCALE, Writer, Writers} from "./Writers.sol";
6
+
7
+ /// @notice Zero-copy view into a calldata block stream.
8
+ /// All positions (`i`, `bound`) are byte offsets relative to the start of the source region.
9
+ /// The absolute calldata location of byte `i` is `offset + i`.
10
+ struct Cur {
11
+ /// @dev Absolute calldata byte offset of the source region start.
12
+ uint offset;
13
+ /// @dev Current read position, relative to the source start.
14
+ uint i;
15
+ /// @dev Total byte length of the source region.
16
+ uint len;
17
+ /// @dev Exclusive upper bound for the current iteration group, set by `primeRun`.
18
+ /// Zero until `primeRun` is called.
19
+ uint bound;
20
+ }
21
+
22
+ using Cursors for Cur;
23
+
24
+ /// @title Cursors
25
+ /// @notice Calldata block stream parser for the rootzero protocol.
26
+ /// A `Cur` is a lightweight view into a slice of `msg.data`; no data is copied.
27
+ /// Blocks are encoded as `[bytes4 key][bytes4 payloadLen][payload]`.
28
+ library Cursors {
29
+ /// @dev Source region contains a block whose declared length exceeds the region boundary,
30
+ /// or a header read would go out of bounds.
31
+ error MalformedBlocks();
32
+ /// @dev Current block key does not match the expected key, or payload size is out of range.
33
+ error InvalidBlock();
34
+ /// @dev `primeRun` found zero blocks of the expected key; the cursor region is empty.
35
+ error ZeroCursor();
36
+ /// @dev `complete` called but the cursor has not consumed exactly up to `bound`.
37
+ error IncompleteCursor();
38
+ /// @dev `primeRun` was called with a zero group size.
39
+ error ZeroGroup();
40
+ /// @dev A recipient field was required but the block or fallback was zero.
41
+ error ZeroRecipient();
42
+ /// @dev A node field was required but the block or fallback was zero.
43
+ error ZeroNode();
44
+ /// @dev A field value did not match the expected value.
45
+ error UnexpectedValue();
46
+ /// @dev Input and output block counts are not proportional to their declared group sizes.
47
+ error BadRatio();
48
+
49
+ // -------------------------------------------------------------------------
50
+ // Cursor construction and navigation
51
+ // -------------------------------------------------------------------------
52
+
53
+ /// @notice Create a cursor backed by a calldata slice.
54
+ /// @param source Calldata slice that forms the block stream.
55
+ /// @return cur Cursor positioned at the beginning of `source`.
56
+ function open(bytes calldata source) internal pure returns (Cur memory cur) {
57
+ uint offset;
58
+ // Extract the absolute calldata offset of `source` using inline assembly,
59
+ // as Solidity does not expose this directly for calldata slices.
60
+ assembly ("memory-safe") {
61
+ offset := source.offset
62
+ }
63
+ cur.offset = offset;
64
+ cur.len = source.length;
65
+ }
66
+
67
+ /// @notice Move the cursor to an absolute position within the source region.
68
+ /// @param cur Cursor to update.
69
+ /// @param i New read position (byte offset relative to source start).
70
+ /// @return Updated cursor with `cur.i == i`.
71
+ function seek(Cur memory cur, uint i) internal pure returns (Cur memory) {
72
+ if (i > cur.len) revert MalformedBlocks();
73
+ cur.i = i;
74
+ return cur;
75
+ }
76
+
77
+ /// @notice Read a block header at position `i` without advancing the cursor.
78
+ /// @param cur Source cursor.
79
+ /// @param i Byte offset of the block header within the source region.
80
+ /// @return key Four-byte block type identifier.
81
+ /// @return len Payload byte length declared in the header.
82
+ function peek(Cur memory cur, uint i) internal pure returns (bytes4 key, uint len) {
83
+ if (i + 8 > cur.len) revert MalformedBlocks();
84
+ uint abs = cur.offset + i;
85
+ key = bytes4(msg.data[abs:abs + 4]);
86
+ len = uint32(bytes4(msg.data[abs + 4:abs + 8]));
87
+ if (i + 8 + len > cur.len) revert MalformedBlocks();
88
+ }
89
+
90
+ /// @notice Validate a block at position `i` and return its payload location.
91
+ /// Does not advance the cursor.
92
+ /// @param cur Source cursor.
93
+ /// @param i Byte offset of the block within the source region.
94
+ /// @param key Expected block type key; reverts if actual key differs.
95
+ /// @param min Minimum acceptable payload length (inclusive).
96
+ /// @param max Maximum acceptable payload length (inclusive); 0 means unbounded.
97
+ /// @return abs Absolute calldata offset of the payload start.
98
+ /// @return next Byte offset of the block immediately following this one (relative to source start).
99
+ function expect(
100
+ Cur memory cur,
101
+ uint i,
102
+ bytes4 key,
103
+ uint min,
104
+ uint max
105
+ ) internal pure returns (uint abs, uint next) {
106
+ (bytes4 current, uint len) = peek(cur, i);
107
+ if (current != key) revert InvalidBlock();
108
+ abs = cur.offset + i + 8;
109
+ next = i + 8 + len;
110
+ if (len < min || (max != 0 && len > max)) revert InvalidBlock();
111
+ }
112
+
113
+ /// @notice Count consecutive blocks of the same key starting at `i`.
114
+ /// @param cur Source cursor.
115
+ /// @param i Starting byte offset within the source region.
116
+ /// @param key Block type to count.
117
+ /// @return total Number of consecutive matching blocks.
118
+ /// @return next Byte offset immediately after the last counted block.
119
+ function countRun(Cur memory cur, uint i, bytes4 key) internal pure returns (uint total, uint next) {
120
+ next = i;
121
+ while (next < cur.len) {
122
+ (bytes4 current, uint len) = peek(cur, next);
123
+ if (current != key) break;
124
+ next += 8 + len;
125
+
126
+ unchecked {
127
+ ++total;
128
+ }
129
+ }
130
+ }
131
+
132
+ /// @notice Initialise the cursor for a grouped iteration pass.
133
+ /// Reads the key of the first block, counts the consecutive run of that key,
134
+ /// stores the run end in `cur.bound`, validates that the count is a
135
+ /// multiple of `group`, and returns both the raw block count and the
136
+ /// normalized quotient (`count / group`).
137
+ /// @param cur Cursor to prime; `cur.bound` is updated in place.
138
+ /// @param group Expected group size (e.g. 1 for single-asset, 2 for paired input/output).
139
+ /// @return key Block type identifier of the run.
140
+ /// @return count Total number of blocks in the run (always a multiple of `group`).
141
+ /// @return quotient Number of groups represented by the run (`count / group`).
142
+ function primeRun(Cur memory cur, uint group) internal pure returns (bytes4 key, uint count, uint quotient) {
143
+ if (group == 0) revert ZeroGroup();
144
+ key = cur.len < 4 ? bytes4(0) : bytes4(msg.data[cur.offset:cur.offset + 4]);
145
+ (count, cur.bound) = countRun(cur, cur.i, key);
146
+ if (count == 0) revert ZeroCursor();
147
+ if (count % group != 0) revert BadRatio();
148
+ quotient = count / group;
149
+ }
150
+
151
+ /// @notice Scan forward from `i` for the first block matching `key`.
152
+ /// @param cur Source cursor.
153
+ /// @param i Starting byte offset for the search.
154
+ /// @param key Block type to find.
155
+ /// @return Byte offset of the matching block, or `cur.len` if not found.
156
+ function find(Cur memory cur, uint i, bytes4 key) internal pure returns (uint) {
157
+ while (i < cur.len) {
158
+ (bytes4 current, uint len) = peek(cur, i);
159
+ if (current == key) return i;
160
+ i += 8 + len;
161
+ }
162
+ return cur.len;
163
+ }
164
+
165
+ /// @notice Scan forward from the current position for the first block matching `key`.
166
+ /// @param cur Source cursor.
167
+ /// @param key Block type to find.
168
+ /// @return Byte offset of the matching block, or `cur.len` if not found.
169
+ function find(Cur memory cur, bytes4 key) internal pure returns (uint) {
170
+ return find(cur, cur.i, key);
171
+ }
172
+
173
+ /// @notice Validate and consume the current block, advancing `cur.i` past it.
174
+ /// @param cur Cursor to advance.
175
+ /// @param key Expected block type key.
176
+ /// @param min Minimum payload length.
177
+ /// @param max Maximum payload length (0 = unbounded).
178
+ /// @return abs Absolute calldata offset of the payload start.
179
+ function consume(Cur memory cur, bytes4 key, uint min, uint max) internal pure returns (uint abs) {
180
+ uint next;
181
+ (abs, next) = expect(cur, cur.i, key, min, max);
182
+ cur.i = next;
183
+ }
184
+
185
+ /// @notice Parse a Bundle block at the current position and return an inner cursor.
186
+ /// Advances `cur.i` past the bundle block.
187
+ /// @param cur Outer cursor; advanced by one bundle block.
188
+ /// @return out Inner cursor scoped to the bundle's embedded block stream.
189
+ function bundle(Cur memory cur) internal pure returns (Cur memory out) {
190
+ (uint abs, uint next) = expect(cur, cur.i, Keys.Bundle, 0, 0);
191
+ uint len = next - (abs - cur.offset);
192
+ out.offset = abs;
193
+ out.len = len;
194
+ cur.i = next;
195
+ }
196
+
197
+ /// @notice Assert that the cursor has consumed exactly up to `bound`.
198
+ /// Reverts with `IncompleteCursor` if `bound` is zero or `cur.i != cur.bound`.
199
+ /// @param cur Cursor to check.
200
+ function complete(Cur memory cur) internal pure {
201
+ if (cur.bound == 0 || cur.i != cur.bound) revert IncompleteCursor();
202
+ }
203
+
204
+ /// @notice Assert completion and finalise a writer in one step.
205
+ /// @param cur Cursor to check.
206
+ /// @param writer Writer to finalise.
207
+ /// @return Trimmed output bytes from the writer.
208
+ function complete(Cur memory cur, Writer memory writer) internal pure returns (bytes memory) {
209
+ if (cur.bound == 0 || cur.i != cur.bound) revert IncompleteCursor();
210
+ return Writers.finish(writer);
211
+ }
212
+
213
+ // -------------------------------------------------------------------------
214
+ // Block factory helpers
215
+ // -------------------------------------------------------------------------
216
+
217
+ /// @notice Encode a block with a single 32-byte payload word.
218
+ /// @param key Block type key.
219
+ /// @param value 32-byte payload.
220
+ /// @return Encoded block bytes.
221
+ function create32(bytes4 key, bytes32 value) internal pure returns (bytes memory) {
222
+ return bytes.concat(key, bytes4(uint32(0x20)), value);
223
+ }
224
+
225
+ /// @notice Encode a block with two 32-byte payload words (64-byte payload).
226
+ /// @param key Block type key.
227
+ /// @param a First payload word.
228
+ /// @param b Second payload word.
229
+ /// @return Encoded block bytes.
230
+ function create64(bytes4 key, bytes32 a, bytes32 b) internal pure returns (bytes memory) {
231
+ return bytes.concat(key, bytes4(uint32(0x40)), a, b);
232
+ }
233
+
234
+ /// @notice Encode a block with three 32-byte payload words (96-byte payload).
235
+ /// @param key Block type key.
236
+ /// @param a First payload word.
237
+ /// @param b Second payload word.
238
+ /// @param c Third payload word.
239
+ /// @return Encoded block bytes.
240
+ function create96(bytes4 key, bytes32 a, bytes32 b, bytes32 c) internal pure returns (bytes memory) {
241
+ return bytes.concat(key, bytes4(uint32(0x60)), a, b, c);
242
+ }
243
+
244
+ /// @notice Encode a block with four 32-byte payload words (128-byte payload).
245
+ /// @param key Block type key.
246
+ /// @param a First payload word.
247
+ /// @param b Second payload word.
248
+ /// @param c Third payload word.
249
+ /// @param d Fourth payload word.
250
+ /// @return Encoded block bytes.
251
+ function create128(bytes4 key, bytes32 a, bytes32 b, bytes32 c, bytes32 d) internal pure returns (bytes memory) {
252
+ return bytes.concat(key, bytes4(uint32(0x80)), a, b, c, d);
253
+ }
254
+
255
+ /// @notice Encode a BOUNTY block.
256
+ /// @param bounty Relayer reward amount.
257
+ /// @param relayer Relayer account identifier.
258
+ /// @return Encoded BOUNTY block bytes.
259
+ function toBountyBlock(uint bounty, bytes32 relayer) internal pure returns (bytes memory) {
260
+ return create64(Keys.Bounty, bytes32(bounty), relayer);
261
+ }
262
+
263
+ /// @notice Encode a BALANCE block.
264
+ /// @param asset Asset identifier.
265
+ /// @param meta Asset metadata slot.
266
+ /// @param amount Token amount.
267
+ /// @return Encoded BALANCE block bytes.
268
+ function toBalanceBlock(bytes32 asset, bytes32 meta, uint amount) internal pure returns (bytes memory) {
269
+ return create96(Keys.Balance, asset, meta, bytes32(amount));
270
+ }
271
+
272
+ /// @notice Encode a CUSTODY block.
273
+ /// @param host Host node ID holding the custody.
274
+ /// @param asset Asset identifier.
275
+ /// @param meta Asset metadata slot.
276
+ /// @param amount Token amount.
277
+ /// @return Encoded CUSTODY block bytes.
278
+ function toCustodyBlock(uint host, bytes32 asset, bytes32 meta, uint amount) internal pure returns (bytes memory) {
279
+ return create128(Keys.Custody, bytes32(host), asset, meta, bytes32(amount));
280
+ }
281
+
282
+ // -------------------------------------------------------------------------
283
+ // Trailing-block helpers (search after bound)
284
+ // -------------------------------------------------------------------------
285
+
286
+ /// @notice Look for a NODE block after the current run boundary and return its value.
287
+ /// Searches from `cur.bound` to the end of the source region.
288
+ /// @param cur Source cursor; `bound` marks the end of the primary run.
289
+ /// @param backup Value to return if no NODE block is found.
290
+ /// @return node Node ID from the NODE block, or `backup` if absent.
291
+ function nodeAfter(Cur memory cur, uint backup) internal pure returns (uint node) {
292
+ uint i = find(cur, cur.bound, Keys.Node);
293
+ if (i == cur.len) return backup;
294
+
295
+ (uint abs, ) = expect(cur, i, Keys.Node, 32, 32);
296
+ return uint(bytes32(msg.data[abs:abs + 32]));
297
+ }
298
+
299
+ /// @notice Look for a RECIPIENT block after the current run boundary and return its value.
300
+ /// Searches from `cur.bound` to the end of the source region.
301
+ /// @param cur Source cursor; `bound` marks the end of the primary run.
302
+ /// @param backup Account to return if no RECIPIENT block is found.
303
+ /// @return account Recipient account from the RECIPIENT block, or `backup` if absent.
304
+ function recipientAfter(Cur memory cur, bytes32 backup) internal pure returns (bytes32 account) {
305
+ uint i = find(cur, cur.bound, Keys.Recipient);
306
+ if (i == cur.len) return backup;
307
+
308
+ (uint abs, ) = expect(cur, i, Keys.Recipient, 32, 32);
309
+ return bytes32(msg.data[abs:abs + 32]);
310
+ }
311
+
312
+ /// @notice Parse the trailing AUTH block and compute the signed message hash.
313
+ /// The AUTH block must occupy the final `Sizes.Auth` bytes of the source region
314
+ /// and must begin after `cur.bound`.
315
+ /// The signed slice covers from `cur.i` up to (but not including) the AUTH proof bytes.
316
+ /// @param cur Source cursor; `bound` marks the end of the primary data region.
317
+ /// @param cid Command ID that the signature must be bound to.
318
+ /// @return hash keccak256 of the signed message slice.
319
+ /// @return deadline Expiry timestamp from the AUTH block.
320
+ /// @return proof Raw proof bytes (layout: `[bytes20 signer][bytes65 sig]`).
321
+ function authLast(
322
+ Cur memory cur,
323
+ uint cid
324
+ ) internal pure returns (bytes32 hash, uint deadline, bytes calldata proof) {
325
+ if (cur.len - cur.i < Sizes.Auth) revert MalformedBlocks();
326
+
327
+ uint i = cur.len - Sizes.Auth;
328
+ if (i < cur.bound) revert MalformedBlocks();
329
+
330
+ (deadline, proof) = expectAuth(cur, i, cid);
331
+ hash = keccak256(msg.data[cur.offset + cur.i:cur.offset + cur.len - Sizes.Proof]);
332
+ }
333
+
334
+ // -------------------------------------------------------------------------
335
+ // unpack* — consume current block and decode payload fields
336
+ // -------------------------------------------------------------------------
337
+
338
+ /// @notice Consume a BALANCE block and return its fields as a struct.
339
+ /// @param cur Cursor; advanced past the block.
340
+ /// @return value Decoded asset, meta, and amount.
341
+ function unpackBalanceValue(Cur memory cur) internal pure returns (AssetAmount memory value) {
342
+ uint abs = consume(cur, Keys.Balance, 96, 96);
343
+ value.asset = bytes32(msg.data[abs:abs + 32]);
344
+ value.meta = bytes32(msg.data[abs + 32:abs + 64]);
345
+ value.amount = uint(bytes32(msg.data[abs + 64:abs + 96]));
346
+ }
347
+
348
+ /// @notice Consume an AMOUNT block and return its fields as a struct.
349
+ /// @param cur Cursor; advanced past the block.
350
+ /// @return value Decoded asset, meta, and amount.
351
+ function unpackAmountValue(Cur memory cur) internal pure returns (AssetAmount memory value) {
352
+ uint abs = consume(cur, Keys.Amount, 96, 96);
353
+ value.asset = bytes32(msg.data[abs:abs + 32]);
354
+ value.meta = bytes32(msg.data[abs + 32:abs + 64]);
355
+ value.amount = uint(bytes32(msg.data[abs + 64:abs + 96]));
356
+ }
357
+
358
+ /// @notice Consume an AMOUNT block and return its fields as separate values.
359
+ /// @param cur Cursor; advanced past the block.
360
+ /// @return asset Asset identifier.
361
+ /// @return meta Asset metadata slot.
362
+ /// @return amount Token amount.
363
+ function unpackAmount(Cur memory cur) internal pure returns (bytes32 asset, bytes32 meta, uint amount) {
364
+ uint abs = consume(cur, Keys.Amount, 96, 96);
365
+ asset = bytes32(msg.data[abs:abs + 32]);
366
+ meta = bytes32(msg.data[abs + 32:abs + 64]);
367
+ amount = uint(bytes32(msg.data[abs + 64:abs + 96]));
368
+ }
369
+
370
+ /// @notice Consume a BALANCE block and return its fields as separate values.
371
+ /// @param cur Cursor; advanced past the block.
372
+ /// @return asset Asset identifier.
373
+ /// @return meta Asset metadata slot.
374
+ /// @return amount Token amount.
375
+ function unpackBalance(Cur memory cur) internal pure returns (bytes32 asset, bytes32 meta, uint amount) {
376
+ uint abs = consume(cur, Keys.Balance, 96, 96);
377
+ asset = bytes32(msg.data[abs:abs + 32]);
378
+ meta = bytes32(msg.data[abs + 32:abs + 64]);
379
+ amount = uint(bytes32(msg.data[abs + 64:abs + 96]));
380
+ }
381
+
382
+ /// @notice Consume a MINIMUM block and return its fields as separate values.
383
+ /// @param cur Cursor; advanced past the block.
384
+ /// @return asset Asset identifier.
385
+ /// @return meta Asset metadata slot.
386
+ /// @return amount Minimum acceptable amount.
387
+ function unpackMinimum(Cur memory cur) internal pure returns (bytes32 asset, bytes32 meta, uint amount) {
388
+ uint abs = consume(cur, Keys.Minimum, 96, 96);
389
+ asset = bytes32(msg.data[abs:abs + 32]);
390
+ meta = bytes32(msg.data[abs + 32:abs + 64]);
391
+ amount = uint(bytes32(msg.data[abs + 64:abs + 96]));
392
+ }
393
+
394
+ /// @notice Consume a MINIMUM block and return its fields as a struct.
395
+ /// @param cur Cursor; advanced past the block.
396
+ /// @return value Decoded asset, meta, and minimum amount.
397
+ function unpackMinimumValue(Cur memory cur) internal pure returns (AssetAmount memory value) {
398
+ uint abs = consume(cur, Keys.Minimum, 96, 96);
399
+ value.asset = bytes32(msg.data[abs:abs + 32]);
400
+ value.meta = bytes32(msg.data[abs + 32:abs + 64]);
401
+ value.amount = uint(bytes32(msg.data[abs + 64:abs + 96]));
402
+ }
403
+
404
+ /// @notice Consume a MAXIMUM block and return its fields as separate values.
405
+ /// @param cur Cursor; advanced past the block.
406
+ /// @return asset Asset identifier.
407
+ /// @return meta Asset metadata slot.
408
+ /// @return amount Maximum allowable spend.
409
+ function unpackMaximum(Cur memory cur) internal pure returns (bytes32 asset, bytes32 meta, uint amount) {
410
+ uint abs = consume(cur, Keys.Maximum, 96, 96);
411
+ asset = bytes32(msg.data[abs:abs + 32]);
412
+ meta = bytes32(msg.data[abs + 32:abs + 64]);
413
+ amount = uint(bytes32(msg.data[abs + 64:abs + 96]));
414
+ }
415
+
416
+ /// @notice Consume a MAXIMUM block and return its fields as a struct.
417
+ /// @param cur Cursor; advanced past the block.
418
+ /// @return value Decoded asset, meta, and maximum amount.
419
+ function unpackMaximumValue(Cur memory cur) internal pure returns (AssetAmount memory value) {
420
+ uint abs = consume(cur, Keys.Maximum, 96, 96);
421
+ value.asset = bytes32(msg.data[abs:abs + 32]);
422
+ value.meta = bytes32(msg.data[abs + 32:abs + 64]);
423
+ value.amount = uint(bytes32(msg.data[abs + 64:abs + 96]));
424
+ }
425
+
426
+ /// @notice Consume a STEP block and return its sub-command invocation fields.
427
+ /// The `req` slice covers any additional payload bytes after the fixed head.
428
+ /// @param cur Cursor; advanced past the block.
429
+ /// @return target Destination node ID for the sub-command.
430
+ /// @return value Native value to forward with the call.
431
+ /// @return req Embedded request bytes for the sub-command.
432
+ function unpackStep(Cur memory cur) internal pure returns (uint target, uint value, bytes calldata req) {
433
+ uint abs = consume(cur, Keys.Step, 64, 0);
434
+ target = uint(bytes32(msg.data[abs:abs + 32]));
435
+ value = uint(bytes32(msg.data[abs + 32:abs + 64]));
436
+ req = msg.data[abs + 64:cur.offset + cur.i];
437
+ }
438
+
439
+ /// @notice Consume a RECIPIENT block and return the account.
440
+ /// @param cur Cursor; advanced past the block.
441
+ /// @return account Destination account identifier.
442
+ function unpackRecipient(Cur memory cur) internal pure returns (bytes32 account) {
443
+ uint abs = consume(cur, Keys.Recipient, 32, 32);
444
+ account = bytes32(msg.data[abs:abs + 32]);
445
+ }
446
+
447
+ /// @notice Consume a PARTY block and return the account.
448
+ /// @param cur Cursor; advanced past the block.
449
+ /// @return account Counter-party account identifier.
450
+ function unpackParty(Cur memory cur) internal pure returns (bytes32 account) {
451
+ uint abs = consume(cur, Keys.Party, 32, 32);
452
+ account = bytes32(msg.data[abs:abs + 32]);
453
+ }
454
+
455
+ /// @notice Consume a RATE block and return the value.
456
+ /// @param cur Cursor; advanced past the block.
457
+ /// @return value Encoded ratio or rate.
458
+ function unpackRate(Cur memory cur) internal pure returns (uint value) {
459
+ uint abs = consume(cur, Keys.Rate, 32, 32);
460
+ value = uint(bytes32(msg.data[abs:abs + 32]));
461
+ }
462
+
463
+ /// @notice Consume a QUANTITY block and return the amount.
464
+ /// @param cur Cursor; advanced past the block.
465
+ /// @return amount Scalar quantity value.
466
+ function unpackQuantity(Cur memory cur) internal pure returns (uint amount) {
467
+ uint abs = consume(cur, Keys.Quantity, 32, 32);
468
+ amount = uint(bytes32(msg.data[abs:abs + 32]));
469
+ }
470
+
471
+ /// @notice Consume an ASSET block and return the asset descriptor fields.
472
+ /// @param cur Cursor; advanced past the block.
473
+ /// @return asset Asset identifier.
474
+ /// @return meta Asset metadata slot.
475
+ function unpackAsset(Cur memory cur) internal pure returns (bytes32 asset, bytes32 meta) {
476
+ uint abs = consume(cur, Keys.Asset, 64, 64);
477
+ asset = bytes32(msg.data[abs:abs + 32]);
478
+ meta = bytes32(msg.data[abs + 32:abs + 64]);
479
+ }
480
+
481
+ /// @notice Consume a FUNDING block and return the host and amount.
482
+ /// @param cur Cursor; advanced past the block.
483
+ /// @return host Host node ID receiving the funding.
484
+ /// @return amount Funding amount.
485
+ function unpackFunding(Cur memory cur) internal pure returns (uint host, uint amount) {
486
+ uint abs = consume(cur, Keys.Funding, 64, 64);
487
+ host = uint(bytes32(msg.data[abs:abs + 32]));
488
+ amount = uint(bytes32(msg.data[abs + 32:abs + 64]));
489
+ }
490
+
491
+ /// @notice Consume a BOUNTY block and return the reward amount and relayer.
492
+ /// @param cur Cursor; advanced past the block.
493
+ /// @return amount Relayer reward amount.
494
+ /// @return relayer Relayer account identifier.
495
+ function unpackBounty(Cur memory cur) internal pure returns (uint amount, bytes32 relayer) {
496
+ uint abs = consume(cur, Keys.Bounty, 64, 64);
497
+ amount = uint(bytes32(msg.data[abs:abs + 32]));
498
+ relayer = bytes32(msg.data[abs + 32:abs + 64]);
499
+ }
500
+
501
+ /// @notice Consume a LISTING block and return its fields as separate values.
502
+ /// @param cur Cursor; advanced past the block.
503
+ /// @return host Host node ID that lists the asset.
504
+ /// @return asset Asset identifier.
505
+ /// @return meta Asset metadata slot.
506
+ function unpackListing(Cur memory cur) internal pure returns (uint host, bytes32 asset, bytes32 meta) {
507
+ uint abs = consume(cur, Keys.Listing, 96, 96);
508
+ host = uint(bytes32(msg.data[abs:abs + 32]));
509
+ asset = bytes32(msg.data[abs + 32:abs + 64]);
510
+ meta = bytes32(msg.data[abs + 64:abs + 96]);
511
+ }
512
+
513
+ /// @notice Consume a LISTING block and return its fields as a struct.
514
+ /// @param cur Cursor; advanced past the block.
515
+ /// @return value Decoded host, asset, and meta.
516
+ function unpackListingValue(Cur memory cur) internal pure returns (HostAsset memory value) {
517
+ uint abs = consume(cur, Keys.Listing, 96, 96);
518
+ value.host = uint(bytes32(msg.data[abs:abs + 32]));
519
+ value.asset = bytes32(msg.data[abs + 32:abs + 64]);
520
+ value.meta = bytes32(msg.data[abs + 64:abs + 96]);
521
+ }
522
+
523
+ /// @notice Consume a NODE block and return the node ID.
524
+ /// @param cur Cursor; advanced past the block.
525
+ /// @return node Node identifier.
526
+ function unpackNode(Cur memory cur) internal pure returns (uint node) {
527
+ uint abs = consume(cur, Keys.Node, 32, 32);
528
+ node = uint(bytes32(msg.data[abs:abs + 32]));
529
+ }
530
+
531
+ /// @notice Consume a ROUTE block and return the raw payload as a calldata slice.
532
+ /// The payload length is variable; the returned slice covers the entire payload.
533
+ /// @param cur Cursor; advanced past the block.
534
+ /// @return data Raw route payload bytes.
535
+ function unpackRoute(Cur memory cur) internal pure returns (bytes calldata data) {
536
+ (uint abs, uint next) = expect(cur, cur.i, Keys.Route, 0, 0);
537
+ data = msg.data[abs:cur.offset + next];
538
+ cur.i = next;
539
+ }
540
+
541
+ /// @notice Consume a ROUTE block with a single uint payload.
542
+ /// @param cur Cursor; advanced past the block.
543
+ /// @return value Decoded uint value.
544
+ function unpackRouteUint(Cur memory cur) internal pure returns (uint value) {
545
+ uint abs = consume(cur, Keys.Route, 32, 32);
546
+ value = uint(bytes32(msg.data[abs:abs + 32]));
547
+ }
548
+
549
+ /// @notice Consume a ROUTE block with two uint payload words.
550
+ /// @param cur Cursor; advanced past the block.
551
+ /// @return a First decoded uint.
552
+ /// @return b Second decoded uint.
553
+ function unpackRoute2Uint(Cur memory cur) internal pure returns (uint a, uint b) {
554
+ uint abs = consume(cur, Keys.Route, 64, 64);
555
+ a = uint(bytes32(msg.data[abs:abs + 32]));
556
+ b = uint(bytes32(msg.data[abs + 32:abs + 64]));
557
+ }
558
+
559
+ /// @notice Consume a ROUTE block with three uint payload words.
560
+ /// @param cur Cursor; advanced past the block.
561
+ /// @return a First decoded uint.
562
+ /// @return b Second decoded uint.
563
+ /// @return c Third decoded uint.
564
+ function unpackRoute3Uint(Cur memory cur) internal pure returns (uint a, uint b, uint c) {
565
+ uint abs = consume(cur, Keys.Route, 96, 96);
566
+ a = uint(bytes32(msg.data[abs:abs + 32]));
567
+ b = uint(bytes32(msg.data[abs + 32:abs + 64]));
568
+ c = uint(bytes32(msg.data[abs + 64:abs + 96]));
569
+ }
570
+
571
+ /// @notice Consume a ROUTE block with a single bytes32 payload.
572
+ /// @param cur Cursor; advanced past the block.
573
+ /// @return value Decoded bytes32.
574
+ function unpackRoute32(Cur memory cur) internal pure returns (bytes32 value) {
575
+ uint abs = consume(cur, Keys.Route, 32, 32);
576
+ value = bytes32(msg.data[abs:abs + 32]);
577
+ }
578
+
579
+ /// @notice Consume a ROUTE block with two bytes32 payload words.
580
+ /// @param cur Cursor; advanced past the block.
581
+ /// @return a First decoded bytes32.
582
+ /// @return b Second decoded bytes32.
583
+ function unpackRoute64(Cur memory cur) internal pure returns (bytes32 a, bytes32 b) {
584
+ uint abs = consume(cur, Keys.Route, 64, 64);
585
+ a = bytes32(msg.data[abs:abs + 32]);
586
+ b = bytes32(msg.data[abs + 32:abs + 64]);
587
+ }
588
+
589
+ /// @notice Consume a ROUTE block with three bytes32 payload words.
590
+ /// @param cur Cursor; advanced past the block.
591
+ /// @return a First decoded bytes32.
592
+ /// @return b Second decoded bytes32.
593
+ /// @return c Third decoded bytes32.
594
+ function unpackRoute96(Cur memory cur) internal pure returns (bytes32 a, bytes32 b, bytes32 c) {
595
+ uint abs = consume(cur, Keys.Route, 96, 96);
596
+ a = bytes32(msg.data[abs:abs + 32]);
597
+ b = bytes32(msg.data[abs + 32:abs + 64]);
598
+ c = bytes32(msg.data[abs + 64:abs + 96]);
599
+ }
600
+
601
+ /// @notice Consume a CUSTODY block and return its fields as a struct.
602
+ /// @param cur Cursor; advanced past the block.
603
+ /// @return value Decoded host, asset, meta, and amount.
604
+ function unpackCustodyValue(Cur memory cur) internal pure returns (HostAmount memory value) {
605
+ uint abs = consume(cur, Keys.Custody, 128, 128);
606
+ value.host = uint(bytes32(msg.data[abs:abs + 32]));
607
+ value.asset = bytes32(msg.data[abs + 32:abs + 64]);
608
+ value.meta = bytes32(msg.data[abs + 64:abs + 96]);
609
+ value.amount = uint(bytes32(msg.data[abs + 96:abs + 128]));
610
+ }
611
+
612
+ /// @notice Consume an ALLOCATION block and return its fields as separate values.
613
+ /// @param cur Cursor; advanced past the block.
614
+ /// @return host Host node ID of the allocation.
615
+ /// @return asset Asset identifier.
616
+ /// @return meta Asset metadata slot.
617
+ /// @return amount Allocated amount.
618
+ function unpackAllocation(Cur memory cur) internal pure returns (uint host, bytes32 asset, bytes32 meta, uint amount) {
619
+ uint abs = consume(cur, Keys.Allocation, 128, 128);
620
+ host = uint(bytes32(msg.data[abs:abs + 32]));
621
+ asset = bytes32(msg.data[abs + 32:abs + 64]);
622
+ meta = bytes32(msg.data[abs + 64:abs + 96]);
623
+ amount = uint(bytes32(msg.data[abs + 96:abs + 128]));
624
+ }
625
+
626
+ /// @notice Consume an ALLOCATION block and return its fields as a struct.
627
+ /// @param cur Cursor; advanced past the block.
628
+ /// @return value Decoded host, asset, meta, and amount.
629
+ function unpackAllocationValue(Cur memory cur) internal pure returns (HostAmount memory value) {
630
+ uint abs = consume(cur, Keys.Allocation, 128, 128);
631
+ value.host = uint(bytes32(msg.data[abs:abs + 32]));
632
+ value.asset = bytes32(msg.data[abs + 32:abs + 64]);
633
+ value.meta = bytes32(msg.data[abs + 64:abs + 96]);
634
+ value.amount = uint(bytes32(msg.data[abs + 96:abs + 128]));
635
+ }
636
+
637
+ /// @notice Consume a TRANSACTION block and return all fields as a struct.
638
+ /// @param cur Cursor; advanced past the block.
639
+ /// @return value Decoded from, to, asset, meta, and amount.
640
+ function unpackTxValue(Cur memory cur) internal pure returns (Tx memory value) {
641
+ uint abs = consume(cur, Keys.Transaction, 160, 160);
642
+ value.from = bytes32(msg.data[abs:abs + 32]);
643
+ value.to = bytes32(msg.data[abs + 32:abs + 64]);
644
+ value.asset = bytes32(msg.data[abs + 64:abs + 96]);
645
+ value.meta = bytes32(msg.data[abs + 96:abs + 128]);
646
+ value.amount = uint(bytes32(msg.data[abs + 128:abs + 160]));
647
+ }
648
+
649
+ // -------------------------------------------------------------------------
650
+ // expect* — validate at given position without advancing cursor
651
+ // -------------------------------------------------------------------------
652
+
653
+ /// @notice Validate an AUTH block at position `i` and extract deadline and proof.
654
+ /// Does not advance the cursor.
655
+ /// @param cur Source cursor.
656
+ /// @param i Byte offset of the AUTH block.
657
+ /// @param cid Command ID that the AUTH block must reference.
658
+ /// @return deadline Expiry timestamp.
659
+ /// @return proof Raw proof bytes (layout: `[bytes20 signer][bytes65 sig]`).
660
+ function expectAuth(Cur memory cur, uint i, uint cid) internal pure returns (uint deadline, bytes calldata proof) {
661
+ (uint abs, uint next) = expect(cur, i, Keys.Auth, 149, 0);
662
+ if (uint(bytes32(msg.data[abs:abs + 32])) != cid) revert UnexpectedValue();
663
+ deadline = uint(bytes32(msg.data[abs + 32:abs + 64]));
664
+ proof = msg.data[abs + 64:cur.offset + next];
665
+ }
666
+
667
+ /// @notice Validate an AMOUNT block at position `i` for a specific asset.
668
+ /// Does not advance the cursor.
669
+ /// @param cur Source cursor.
670
+ /// @param i Byte offset of the AMOUNT block.
671
+ /// @param asset Expected asset identifier.
672
+ /// @param meta Expected metadata slot.
673
+ /// @return amount Token amount from the block.
674
+ function expectAmount(Cur memory cur, uint i, bytes32 asset, bytes32 meta) internal pure returns (uint amount) {
675
+ (uint abs, ) = expect(cur, i, Keys.Amount, 96, 96);
676
+ if (bytes32(msg.data[abs:abs + 32]) != asset) revert UnexpectedValue();
677
+ if (bytes32(msg.data[abs + 32:abs + 64]) != meta) revert UnexpectedValue();
678
+ amount = uint(bytes32(msg.data[abs + 64:abs + 96]));
679
+ }
680
+
681
+ /// @notice Validate a BALANCE block at position `i` for a specific asset.
682
+ /// Does not advance the cursor.
683
+ /// @param cur Source cursor.
684
+ /// @param i Byte offset of the BALANCE block.
685
+ /// @param asset Expected asset identifier.
686
+ /// @param meta Expected metadata slot.
687
+ /// @return amount Token amount from the block.
688
+ function expectBalance(Cur memory cur, uint i, bytes32 asset, bytes32 meta) internal pure returns (uint amount) {
689
+ (uint abs, ) = expect(cur, i, Keys.Balance, 96, 96);
690
+ if (bytes32(msg.data[abs:abs + 32]) != asset) revert UnexpectedValue();
691
+ if (bytes32(msg.data[abs + 32:abs + 64]) != meta) revert UnexpectedValue();
692
+ amount = uint(bytes32(msg.data[abs + 64:abs + 96]));
693
+ }
694
+
695
+ /// @notice Validate a MINIMUM block at position `i` for a specific asset.
696
+ /// Does not advance the cursor.
697
+ /// @param cur Source cursor.
698
+ /// @param i Byte offset of the MINIMUM block.
699
+ /// @param asset Expected asset identifier.
700
+ /// @param meta Expected metadata slot.
701
+ /// @return amount Minimum amount from the block.
702
+ function expectMinimum(Cur memory cur, uint i, bytes32 asset, bytes32 meta) internal pure returns (uint amount) {
703
+ (uint abs, ) = expect(cur, i, Keys.Minimum, 96, 96);
704
+ if (bytes32(msg.data[abs:abs + 32]) != asset) revert UnexpectedValue();
705
+ if (bytes32(msg.data[abs + 32:abs + 64]) != meta) revert UnexpectedValue();
706
+ amount = uint(bytes32(msg.data[abs + 64:abs + 96]));
707
+ }
708
+
709
+ /// @notice Validate a MAXIMUM block at position `i` for a specific asset.
710
+ /// Does not advance the cursor.
711
+ /// @param cur Source cursor.
712
+ /// @param i Byte offset of the MAXIMUM block.
713
+ /// @param asset Expected asset identifier.
714
+ /// @param meta Expected metadata slot.
715
+ /// @return amount Maximum amount from the block.
716
+ function expectMaximum(Cur memory cur, uint i, bytes32 asset, bytes32 meta) internal pure returns (uint amount) {
717
+ (uint abs, ) = expect(cur, i, Keys.Maximum, 96, 96);
718
+ if (bytes32(msg.data[abs:abs + 32]) != asset) revert UnexpectedValue();
719
+ if (bytes32(msg.data[abs + 32:abs + 64]) != meta) revert UnexpectedValue();
720
+ amount = uint(bytes32(msg.data[abs + 64:abs + 96]));
721
+ }
722
+
723
+ /// @notice Validate a CUSTODY block at position `i` for a specific host.
724
+ /// Does not advance the cursor.
725
+ /// @param cur Source cursor.
726
+ /// @param i Byte offset of the CUSTODY block.
727
+ /// @param host Expected host node ID.
728
+ /// @return value Decoded asset, meta, and amount (host is not returned; it was validated).
729
+ function expectCustody(Cur memory cur, uint i, uint host) internal pure returns (AssetAmount memory value) {
730
+ (uint abs, ) = expect(cur, i, Keys.Custody, 128, 128);
731
+ if (uint(bytes32(msg.data[abs:abs + 32])) != host) revert UnexpectedValue();
732
+ value.asset = bytes32(msg.data[abs + 32:abs + 64]);
733
+ value.meta = bytes32(msg.data[abs + 64:abs + 96]);
734
+ value.amount = uint(bytes32(msg.data[abs + 96:abs + 128]));
735
+ }
736
+
737
+ // -------------------------------------------------------------------------
738
+ // require* — validate + advance (like consume with content checks)
739
+ // -------------------------------------------------------------------------
740
+
741
+ /// @notice Consume an AUTH block at the current position and verify the command ID.
742
+ /// @param cur Cursor; advanced past the block.
743
+ /// @param cid Expected command ID.
744
+ /// @return deadline Expiry timestamp.
745
+ /// @return proof Raw proof bytes.
746
+ function requireAuth(Cur memory cur, uint cid) internal pure returns (uint deadline, bytes calldata proof) {
747
+ (deadline, proof) = expectAuth(cur, cur.i, cid);
748
+ cur.i += 64 + proof.length;
749
+ }
750
+
751
+ /// @notice Consume an AMOUNT block and assert it matches the expected asset and meta.
752
+ /// @param cur Cursor; advanced past the block.
753
+ /// @param asset Expected asset identifier.
754
+ /// @param meta Expected metadata slot.
755
+ /// @return amount Token amount from the block.
756
+ function requireAmount(Cur memory cur, bytes32 asset, bytes32 meta) internal pure returns (uint amount) {
757
+ amount = expectAmount(cur, cur.i, asset, meta);
758
+ cur.i += 104;
759
+ }
760
+
761
+ /// @notice Consume a BALANCE block and assert it matches the expected asset and meta.
762
+ /// @param cur Cursor; advanced past the block.
763
+ /// @param asset Expected asset identifier.
764
+ /// @param meta Expected metadata slot.
765
+ /// @return amount Token amount from the block.
766
+ function requireBalance(Cur memory cur, bytes32 asset, bytes32 meta) internal pure returns (uint amount) {
767
+ amount = expectBalance(cur, cur.i, asset, meta);
768
+ cur.i += 104;
769
+ }
770
+
771
+ /// @notice Consume a MINIMUM block and assert it matches the expected asset and meta.
772
+ /// @param cur Cursor; advanced past the block.
773
+ /// @param asset Expected asset identifier.
774
+ /// @param meta Expected metadata slot.
775
+ /// @return amount Minimum amount from the block.
776
+ function requireMinimum(Cur memory cur, bytes32 asset, bytes32 meta) internal pure returns (uint amount) {
777
+ amount = expectMinimum(cur, cur.i, asset, meta);
778
+ cur.i += 104;
779
+ }
780
+
781
+ /// @notice Consume a MAXIMUM block and assert it matches the expected asset and meta.
782
+ /// @param cur Cursor; advanced past the block.
783
+ /// @param asset Expected asset identifier.
784
+ /// @param meta Expected metadata slot.
785
+ /// @return amount Maximum amount from the block.
786
+ function requireMaximum(Cur memory cur, bytes32 asset, bytes32 meta) internal pure returns (uint amount) {
787
+ amount = expectMaximum(cur, cur.i, asset, meta);
788
+ cur.i += 104;
789
+ }
790
+
791
+ /// @notice Consume a CUSTODY block and assert it belongs to the expected host.
792
+ /// @param cur Cursor; advanced past the block.
793
+ /// @param host Expected host node ID.
794
+ /// @return value Decoded asset, meta, and amount.
795
+ function requireCustody(Cur memory cur, uint host) internal pure returns (AssetAmount memory value) {
796
+ value = expectCustody(cur, cur.i, host);
797
+ cur.i += 136;
798
+ }
799
+
800
+ }