@rootzero/contracts 1.23.0 → 1.25.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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,84 @@
3
3
  Until the protocol reaches integration-stable status, minor versions may include
4
4
  breaking API changes. Breaking changes are called out explicitly.
5
5
 
6
+ Add all changes made after a release to `Unreleased`. Published version
7
+ sections are immutable and must continue to describe the tagged release.
8
+
9
+ ## Unreleased
10
+
11
+ ## 1.25.0
12
+
13
+ ### Breaking Changes
14
+
15
+ - Changed `Pipeline.pipe` to accept a scalar `uint` budget and return the
16
+ remaining scalar budget. The standalone mutable `Budget` type remains
17
+ available for other funded execution flows.
18
+ - Split the pipeline hook from its standard implementation. `PipePayablePort`
19
+ now inherits only `PipeHook`; hosts that want the built-in pipeline must
20
+ compose `Pipeline` explicitly, while custom hosts may implement the hook
21
+ without inheriting the standard dispatch and posting implementation.
22
+ - Changed `Decoders.open` to wrap the complete non-empty calldata source as an
23
+ ungrouped cursor. Code that needs the former counted first-homogeneous-run
24
+ behavior must use the new `Decoders.batch` helper.
25
+
26
+ ### Added
27
+
28
+ - Added scalar `Budgets.useValue` and `Budgets.useResourceValue` overloads, and
29
+ `Executions.drainBudget` for transferring an execution budget as a `uint`.
30
+ - Added `PipeHook` to the `Core.sol` and `Endpoints.sol` package barrels.
31
+ - Added `Blocks.expectKey` and key-only `Decoders.consume` and
32
+ `Executions.consume` overloads for validating blocks whose payload shape is
33
+ established by specialized decoding logic.
34
+ - Added `Blocks.require1`, `require2`, `require4`, `require8`, and `require16`,
35
+ completing the fixed-width validation family alongside `require32`.
36
+
37
+ ### Changed
38
+
39
+ - Specialized built-in composite block unpackers to validate their known keys
40
+ directly instead of constructing and evaluating generic specifications.
41
+ - Specialized fixed-width block validation, empty-block inspection, and
42
+ key-only `takeBlock` paths to avoid redundant specification and calldata
43
+ work.
44
+ - Pipeline execution now validates the complete STEP source and rejects a
45
+ trailing block with a different key instead of silently stopping after the
46
+ first homogeneous run.
47
+ - Simplified internal debit-account output allocation to use the fixed-width
48
+ input size directly.
49
+
50
+ ## 1.24.0
51
+
52
+ ### Breaking Changes
53
+
54
+ - Renamed the existing `Position`-consuming `repay` and `repayPayable`
55
+ commands to `repayPosition` and `repayPositionPayable`. Their state
56
+ transition remains `Position` to `Balance`; the new commands using the old
57
+ names consume `Debt` and return empty state. Command selectors and node IDs
58
+ for position repayment therefore change.
59
+
60
+ ### Added
61
+
62
+ - Added the standalone `Debt { liability, debt }` pipeline state, its standard
63
+ block key and schema, and codec support across blocks, readers, writers,
64
+ decoders, and execution helpers.
65
+ - Added `repay` and `repayPayable` commands that consume `Debt` state and return
66
+ empty state.
67
+ - Added `RepayInternal` for dispatching the non-funded `repay` command directly
68
+ against memory-backed pipeline state.
69
+ - Added unchecked `Blocks.read1`, `read2`, `read8`, and `read16` absolute
70
+ calldata helpers, completing the power-of-two family alongside `read4` and
71
+ `read32`.
72
+ - Added the standalone `ensureContract` utility and shared `InvalidContract`
73
+ error for uniformly requiring addresses with deployed bytecode.
74
+ - Added the free `enforceSender` access helper for requiring an exact
75
+ `msg.sender` with the shared `AccessDenied` error.
76
+ - Documented descriptor flag bits 6 and 7 as endpoint-defined custom flags;
77
+ bits 2 through 5 remain reserved for future protocol flags.
78
+
79
+ ### Changed
80
+
81
+ - Repayment commands now publish the canonical `Actions.Repay` annotation
82
+ instead of `Actions.Settle`.
83
+
6
84
  ## 1.23.0
7
85
 
8
86
  ### Breaking Changes
package/Codec.sol CHANGED
@@ -4,7 +4,7 @@ pragma solidity ^0.8.33;
4
4
  // Aggregator: re-exports the complete block encoding and decoding surface.
5
5
  // Import this file for low-level codec extensions and direct stream processing.
6
6
 
7
- import { AssetAmount, AccountAsset, HostAsset, AccountAmount, HostAmount, HostAccountAsset, HostAccountAmount, Position, Tx } from "./core/Types.sol";
7
+ import { AssetAmount, AccountAsset, HostAsset, AccountAmount, HostAmount, HostAccountAsset, HostAccountAmount, Debt, Position, Tx } from "./core/Types.sol";
8
8
  import { Keys } from "./codec/Keys.sol";
9
9
  import { Sizes, Specs } from "./codec/Specs.sol";
10
10
  import { Descriptors, Flags } from "./codec/Descriptors.sol";
package/Commands.sol CHANGED
@@ -12,4 +12,4 @@ import {Blocks} from "./codec/Blocks.sol";
12
12
  import {Sizes, Specs} from "./codec/Specs.sol";
13
13
  import {Decoders} from "./codec/Decoders.sol";
14
14
  import {Cursors, Cur} from "./utils/Cursors.sol";
15
- import {AssetAmount, AccountAsset, HostAsset, AccountAmount, HostAmount, HostAccountAsset, HostAccountAmount, Position, Tx} from "./core/Types.sol";
15
+ import {AssetAmount, AccountAsset, HostAsset, AccountAmount, HostAmount, HostAccountAsset, HostAccountAmount, Debt, Position, Tx} from "./core/Types.sol";
package/Core.sol CHANGED
@@ -7,18 +7,18 @@ pragma solidity ^0.8.33;
7
7
  import { Action } from "./annotations/Action.sol";
8
8
  import { Label } from "./annotations/Label.sol";
9
9
  import { Schema } from "./annotations/Schema.sol";
10
- import { AccessDenied, AdminAccess, CallerAccess, CommanderAccess, CommanderNotAllowed, GuardianAccess, NodeAccess, TrustAccess } from "./core/Access.sol";
10
+ import { AccessDenied, AdminAccess, CallerAccess, CommanderAccess, CommanderNotAllowed, enforceSender, GuardianAccess, NodeAccess, TrustAccess } from "./core/Access.sol";
11
11
  import { Balances, InsufficientFunds } from "./core/Balances.sol";
12
12
  import { Escrows, InsufficientEscrow } from "./core/Escrows.sol";
13
13
  import { NativeAsset, Runtime } from "./core/Runtime.sol";
14
14
  import { Admins, CommandHost, Guardians, Host, HostIntroduction, IHostIntroduction } from "./core/Host.sol";
15
15
  import { CommandCalls, FailedCall, NodeCalls, PortCalls, RawNodeCalls } from "./core/Calls.sol";
16
16
  import { EndpointBase, InputEndpointBase } from "./core/Endpoint.sol";
17
- import { Pipeline } from "./core/Pipeline.sol";
17
+ import { PipeHook, Pipeline } from "./core/Pipeline.sol";
18
18
  import { Budget, Budgets } from "./execution/Budget.sol";
19
19
  import { CreditAccountHook, DebitAccountHook, PostHook, RepayHook, SettleHook, Settlement } from "./core/Settlement.sol";
20
20
  import { Portal } from "./core/Portal.sol";
21
- import { AssetAmount, AccountAsset, HostAsset, AccountAmount, HostAmount, HostAccountAsset, HostAccountAmount, Position, Tx } from "./core/Types.sol";
21
+ import { AssetAmount, AccountAsset, HostAsset, AccountAmount, HostAmount, HostAccountAsset, HostAccountAmount, Debt, Position, Tx } from "./core/Types.sol";
22
22
  import { Validator } from "./core/Validator.sol";
23
23
 
24
24
 
package/Endpoints.sol CHANGED
@@ -6,6 +6,7 @@ pragma solidity ^0.8.33;
6
6
 
7
7
  // Shared endpoint hooks
8
8
  import {Flags} from "./codec/Descriptors.sol";
9
+ import {PipeHook} from "./core/Pipeline.sol";
9
10
  import {CreditAccountHook, DebitAccountHook, PostHook, RepayHook, SettleHook} from "./core/Settlement.sol";
10
11
 
11
12
  // Commands
@@ -18,7 +19,14 @@ import {Deposit, DepositHook, DepositPayable, DepositPayableHook} from "./comman
18
19
  import {Payout, PayoutHook} from "./commands/Payout.sol";
19
20
  import {Provision, ProvisionHook, ProvisionPayable, ProvisionPayableHook} from "./commands/Provision.sol";
20
21
  import {RecoverPayable, RecoverPayableHook} from "./commands/Recover.sol";
21
- import {Repay, RepayPayable, RepayPayableHook} from "./commands/Repay.sol";
22
+ import {
23
+ Repay,
24
+ RepayInternal,
25
+ RepayPayable,
26
+ RepayPayableHook,
27
+ RepayPosition,
28
+ RepayPositionPayable
29
+ } from "./commands/Repay.sol";
22
30
  import {RelayPayable, RelayBalancePayable, RelayPayableHook} from "./commands/Relay.sol";
23
31
  import {Settle, SettlePayable, SettlePayableHook, SettleInternal} from "./commands/Settle.sol";
24
32
  import {Withdraw, WithdrawHook} from "./commands/Withdraw.sol";
package/README.md CHANGED
@@ -265,22 +265,24 @@ and return it, forward it intact, or revert. A command must never succeed while
265
265
  silently ignoring or dropping supplied state. Commands that declare
266
266
  `Specs.Empty` state therefore reject any non-empty state, while commands that
267
267
  accept state validate the complete stream against their declared state schema.
268
- This is especially important for `#position`, because dropping a position could
269
- silently discard both live value and an outstanding debt requirement.
270
-
271
- The input carries instructions; the state carries live value. While a
272
- sequence of commands executes, `#balance`, `#custody`, and `#position` blocks
273
- in the state are the value being moved — produced by one command, consumed by
274
- the next. A position carries an asset-liability pair as
268
+ This is especially important for `#debt` and `#position`, because dropping
269
+ either could silently discard an outstanding debt requirement.
270
+
271
+ The input carries instructions; the state carries live value. While a sequence
272
+ of commands executes, `#balance`, `#debt`, `#custody`, and `#position` blocks in
273
+ the state are the value being moved — produced by one command, consumed by the
274
+ next. Balance carries `{ asset, amount }`, debt carries `{ liability, debt }`,
275
+ and position carries their flat combination
275
276
  `{ asset, amount, liability, debt }`.
276
277
 
277
- `#position` is general live state rather than a lending-specific debt record.
278
- It pairs value acquired or controlled with value owed or required. A command
279
- may preserve or replace either side and return the resulting position for the
280
- next step; `settle` terminally consumes the pair. This supports swaps,
278
+ `#debt` and `#position` are general live state rather than persisted
279
+ lending-specific debt records. Debt carries value owed or required; position
280
+ pairs that liability with value acquired or controlled. A command may preserve
281
+ or replace either side and return the resulting state for the next step;
282
+ `settle` terminally consumes a position pair. This supports swaps,
281
283
  borrowing, refinancing, collateral changes, callback obligations, cross-host
282
- claims, fees, netting, and other multi-step operations. A position is a
283
- transient representation and does not itself create or erase an obligation
284
+ claims, fees, netting, and other multi-step operations. Debt and position are
285
+ transient representations and do not themselves create or erase an obligation
284
286
  recorded by an external system.
285
287
 
286
288
  The standard `Deposit` mixin shows the canonical shape: open and validate both
@@ -330,7 +332,9 @@ abstract contract MyCommand is CommandBase {
330
332
 
331
333
  The final argument is a packed flags byte. Pass `0` for an ordinary endpoint,
332
334
  or compose values such as `Flags.Funded`, `Flags.Admin`, and
333
- `Flags.AdminFunded` from the command or endpoint package entry point.
335
+ `Flags.AdminFunded` from the command or endpoint package entry point. Bits 6
336
+ and 7 are reserved for endpoint-defined custom flags. Bits 2 through 5 remain
337
+ reserved for future protocol flags.
334
338
 
335
339
  The standard commands cover the common ledger movements: `deposit` and
336
340
  `depositPayable` (external funds in), `settlePayable` (funded settlement),
@@ -338,7 +342,9 @@ The standard commands cover the common ledger movements: `deposit` and
338
342
  `debitAccount` and `creditAccount` (internal movements), `payout` (deliver
339
343
  state to other accounts), `allocate` (turn balance state into custody),
340
344
  `provision` (provision custody from an external allocation), `settle` (consume
341
- asset-liability position state), `relayPayable` (relay a pipeline without
345
+ asset-liability position state), `repay` and `repayPayable` (consume standalone
346
+ debt state), `repayPosition` and `repayPositionPayable` (repay a position's debt
347
+ and return its asset as balance state), `relayPayable` (relay a pipeline without
342
348
  state), and `relayBalancePayable` (relay balance state and a pipeline to another
343
349
  portal).
344
350
 
@@ -360,13 +366,15 @@ running the next step. This is the core of `Pipeline.pipe`:
360
366
  ```solidity
361
367
  while (cur.more()) {
362
368
  (uint cmd, uint resources, bytes calldata input) = cur.unpackStep();
369
+ uint128 value;
370
+ (budget, value) = Budgets.useResourceValue(budget, resources);
363
371
  Reader memory transactions;
364
372
  (state, transactions.source) = dispatch(
365
373
  cmd,
366
374
  account,
367
375
  state,
368
376
  input,
369
- budget.useResourceValue(resources)
377
+ value
370
378
  );
371
379
  while (transactions.more()) {
372
380
  (bytes32 from, bytes32 to, bytes32 asset, uint amount) = transactions.unpackTransaction();
@@ -376,6 +384,9 @@ while (cur.more()) {
376
384
  if (state.length != 0) revert UnexpectedState();
377
385
  ```
378
386
 
387
+ `Pipeline.pipe` takes the available native-value budget as a `uint` and returns
388
+ the remaining budget after every step has executed.
389
+
379
390
  A transfer, for instance, is a two-step pipeline: `debitAccount` turns an
380
391
  `#amount` input into `#balance` state, and `payout` consumes that state
381
392
  toward a recipient. Because a pipeline is just blocks, it is also the unit of
@@ -384,12 +395,12 @@ portal adapter (on EVM, the low 128 bits are native value in wei, drawn from a
384
395
  shared budget), so the same pipeline bytes are meaningful to every port.
385
396
 
386
397
  Hosts that implement a pipeline locally can inherit `DebitAccountInternal`,
387
- `CreditAccountInternal`, and `SettleInternal` to advertise the canonical command
388
- endpoints while routing their local command IDs through `executeDebitAccount`,
389
- `executeCreditAccount`, and `executeSettle`. These adapters consume the
390
- memory-backed pipeline state directly and avoid an external self-call. Pass the
391
- step value into each adapter; all three reject nonzero value because the
392
- commands are non-funded.
398
+ `CreditAccountInternal`, `SettleInternal`, and `RepayInternal` to advertise the
399
+ canonical command endpoints while routing their local command IDs through
400
+ `executeDebitAccount`, `executeCreditAccount`, `executeSettle`, and
401
+ `executeRepay`. These adapters consume the memory-backed pipeline state directly
402
+ and avoid an external self-call. Pass the step value into each adapter; all four
403
+ reject nonzero value because the commands are non-funded.
393
404
 
394
405
  Positions also support backward-composed pipelines. In an exact-output route,
395
406
  the asset side can represent the desired result while the liability side
@@ -476,11 +487,11 @@ names, access sets, balances — from logs alone, with no artifact files.
476
487
  Import from the package entry points rather than deep paths:
477
488
 
478
489
  - `@rootzero/contracts/Core.sol` — `Host`, access control, `Balances`,
479
- `Settlement`, `Pipeline`, `Portal`, validator
490
+ `Settlement`, `PipeHook`, `Pipeline`, `Portal`, validator
480
491
  - `@rootzero/contracts/Commands.sol` — `CommandBase`, `Execution`, `Flags`,
481
492
  codec helpers, and shared value types for authoring custom commands
482
493
  - `@rootzero/contracts/Endpoints.sol` — command, admin, port, guard, and query
483
- mixins, their hooks, and `Flags`
494
+ mixins, their hooks (including `PipeHook`), and `Flags`
484
495
  - `@rootzero/contracts/Codec.sol` — `Blocks`, calldata `Cur`/`Cursors`, memory
485
496
  `Reader`/`Readers`, `Writers`, `Schemas`, `Descriptors`, `Flags`, `Keys`, and
486
497
  `Specs`
package/Utils.sol CHANGED
@@ -12,7 +12,7 @@ import { ECDSA } from "./utils/ECDSA.sol";
12
12
  import { Ids } from "./utils/Ids.sol";
13
13
  import { Nodes } from "./utils/Nodes.sol";
14
14
  import { Layout } from "./utils/Layout.sol";
15
- import { addrOr, applyBps, beforeBps, bytes32ToInt, bytes32ToString, clear8, clear16, clear32, clear64, divisible, ensureAddr, hash32, intToBytes32, isFamily, matchesBase, MAX_BPS, max8, max16, max24, max32, max40, max64, max96, max128, max160, NotDivisible, replace8, replace16, replace32, replace64, retryTicket, toLocalBase, toUnspecifiedBase, ValueOverflow, ZeroAddress } from "./utils/Utils.sol";
15
+ import { addrOr, applyBps, beforeBps, bytes32ToInt, bytes32ToString, clear8, clear16, clear32, clear64, divisible, ensureAddr, ensureContract, hash32, intToBytes32, InvalidContract, isFamily, matchesBase, MAX_BPS, max8, max16, max24, max32, max40, max64, max96, max128, max160, NotDivisible, replace8, replace16, replace32, replace64, retryTicket, toLocalBase, toUnspecifiedBase, ValueOverflow, ZeroAddress } from "./utils/Utils.sol";
16
16
 
17
17
 
18
18
 
@@ -18,13 +18,7 @@ abstract contract Schema is Runtime, AnnotationEvent {
18
18
  /// @param hint Initial per-block payload capacity.
19
19
  /// @param body Schema DSL string describing the block payload body.
20
20
  /// @return spec The context-local block specification.
21
- function schema(
22
- uint32 key,
23
- uint32 min,
24
- uint32 max,
25
- uint32 hint,
26
- string memory body
27
- ) internal returns (uint spec) {
21
+ function schema(uint32 key, uint32 min, uint32 max, uint32 hint, string memory body) internal returns (uint spec) {
28
22
  return schema(key, min, max, hint, body, bytes32(0));
29
23
  }
30
24
 
package/codec/Blocks.sol CHANGED
@@ -100,16 +100,33 @@ library Blocks {
100
100
  end = body + len;
101
101
  }
102
102
 
103
+ /// @notice Validate a known block key and return its payload bounds.
104
+ /// @dev DANGER: This performs an unchecked calldata read and validates only
105
+ /// the key. The caller must validate the known payload shape and returned end.
106
+ /// @param abs Absolute calldata position of the header.
107
+ /// @param key Expected block key.
108
+ /// @return body Absolute position of the first payload byte.
109
+ /// @return end Absolute position immediately after the payload.
110
+ function expectKey(uint abs, bytes4 key) internal pure returns (uint body, uint end) {
111
+ uint len = header(abs, key);
112
+ unchecked {
113
+ body = abs + Sizes.Header;
114
+ end = body + len;
115
+ }
116
+ }
117
+
103
118
  /// @dev Validate the key and exact payload size of a fixed-width block.
104
119
  /// @param abs Absolute calldata position of the header.
105
- /// @param spec Expected block specification.
120
+ /// @param key Expected block key.
106
121
  /// @param size Expected payload length.
107
122
  /// @return body Absolute position of the payload.
108
123
  /// @return end Absolute position after the payload.
109
- function expectFixed(uint abs, uint spec, uint size) private pure returns (uint body, uint end) {
110
- if (header(abs, Specs.key(spec)) != size) revert InvalidBlock();
111
- body = abs + Sizes.Header;
112
- end = body + size;
124
+ function expectFixed(uint abs, bytes4 key, uint size) private pure returns (uint body, uint end) {
125
+ if (header(abs, key) != size) revert InvalidBlock();
126
+ unchecked {
127
+ body = abs + Sizes.Header;
128
+ end = body + size;
129
+ }
113
130
  }
114
131
 
115
132
  /// @notice Validate an empty block at an absolute calldata position.
@@ -139,8 +156,9 @@ library Blocks {
139
156
  /// @param key Expected block key.
140
157
  /// @return Whether the expected key occurs with a zero-length payload.
141
158
  function isEmpty(uint abs, uint end, bytes4 key) internal pure returns (bool) {
142
- if (!hasAt(abs, end, key)) return false;
143
- return uint32(uint(read32(abs)) >> 192) == 0;
159
+ if (abs > end || Sizes.Header > end - abs) return false;
160
+ uint head = uint(read32(abs));
161
+ return uint32(head >> 224) == uint32(key) && uint32(head >> 192) == 0;
144
162
  }
145
163
 
146
164
  /// @notice Find the first block with `key` at or after absolute position `abs`.
@@ -382,6 +400,22 @@ library Blocks {
382
400
  }
383
401
  }
384
402
 
403
+ /// @notice Write a DEBT block at `i`.
404
+ /// @dev DANGER: Unchecked memory write. Reserve `Sizes.B64` bytes first.
405
+ /// @param dst Destination buffer.
406
+ /// @param i Relative write position.
407
+ /// @param liability Liability identifier to encode.
408
+ /// @param debt Debt quantity to encode.
409
+ function writeDebt(bytes memory dst, uint i, bytes32 liability, uint debt) internal pure {
410
+ uint spec = Specs.Debt;
411
+ assembly ("memory-safe") {
412
+ let p := add(add(dst, 0x20), i)
413
+ mstore(p, spec)
414
+ mstore(add(p, 0x08), liability)
415
+ mstore(add(p, 0x28), debt)
416
+ }
417
+ }
418
+
385
419
  /// @notice Write a HOST_ASSET block at `i`.
386
420
  /// @dev DANGER: Unchecked memory write. Reserve `Sizes.B64` bytes first.
387
421
  /// @param dst Destination buffer.
@@ -1014,14 +1048,44 @@ library Blocks {
1014
1048
 
1015
1049
  // Raw reads
1016
1050
 
1051
+ /// @notice Read one byte from an absolute calldata position.
1052
+ /// @dev DANGER: Unchecked calldata read. Values beyond calldata are zero-padded.
1053
+ /// @param abs Absolute calldata position.
1054
+ /// @return value Decoded one-byte value.
1055
+ function read1(uint abs) internal pure returns (bytes1 value) {
1056
+ return bytes1(read32(abs));
1057
+ }
1058
+
1059
+ /// @notice Read two bytes from an absolute calldata position.
1060
+ /// @dev DANGER: Unchecked calldata read. Values beyond calldata are zero-padded.
1061
+ /// @param abs Absolute calldata position.
1062
+ /// @return value Decoded two-byte value.
1063
+ function read2(uint abs) internal pure returns (bytes2 value) {
1064
+ return bytes2(read32(abs));
1065
+ }
1066
+
1017
1067
  /// @notice Read four bytes from an absolute calldata position.
1018
1068
  /// @dev DANGER: Unchecked calldata read. Values beyond calldata are zero-padded.
1019
1069
  /// @param abs Absolute calldata position.
1020
1070
  /// @return value Decoded four-byte value.
1021
1071
  function read4(uint abs) internal pure returns (bytes4 value) {
1022
- assembly ("memory-safe") {
1023
- value := calldataload(abs)
1024
- }
1072
+ return bytes4(read32(abs));
1073
+ }
1074
+
1075
+ /// @notice Read eight bytes from an absolute calldata position.
1076
+ /// @dev DANGER: Unchecked calldata read. Values beyond calldata are zero-padded.
1077
+ /// @param abs Absolute calldata position.
1078
+ /// @return value Decoded eight-byte value.
1079
+ function read8(uint abs) internal pure returns (bytes8 value) {
1080
+ return bytes8(read32(abs));
1081
+ }
1082
+
1083
+ /// @notice Read sixteen bytes from an absolute calldata position.
1084
+ /// @dev DANGER: Unchecked calldata read. Values beyond calldata are zero-padded.
1085
+ /// @param abs Absolute calldata position.
1086
+ /// @return value Decoded sixteen-byte value.
1087
+ function read16(uint abs) internal pure returns (bytes16 value) {
1088
+ return bytes16(read32(abs));
1025
1089
  }
1026
1090
 
1027
1091
  /// @notice Read one word from an absolute calldata position.
@@ -1034,6 +1098,46 @@ library Blocks {
1034
1098
  }
1035
1099
  }
1036
1100
 
1101
+ /// @notice Require the byte at an absolute calldata position to match `expected`.
1102
+ /// @dev DANGER: Unchecked calldata read. Values beyond calldata are zero-padded.
1103
+ /// @param abs Absolute calldata position.
1104
+ /// @param expected Expected byte.
1105
+ function require1(uint abs, bytes1 expected) internal pure {
1106
+ if (read1(abs) != expected) revert UnexpectedValue();
1107
+ }
1108
+
1109
+ /// @notice Require the two bytes at an absolute calldata position to match `expected`.
1110
+ /// @dev DANGER: Unchecked calldata read. Values beyond calldata are zero-padded.
1111
+ /// @param abs Absolute calldata position.
1112
+ /// @param expected Expected two-byte value.
1113
+ function require2(uint abs, bytes2 expected) internal pure {
1114
+ if (read2(abs) != expected) revert UnexpectedValue();
1115
+ }
1116
+
1117
+ /// @notice Require the four bytes at an absolute calldata position to match `expected`.
1118
+ /// @dev DANGER: Unchecked calldata read. Values beyond calldata are zero-padded.
1119
+ /// @param abs Absolute calldata position.
1120
+ /// @param expected Expected four-byte value.
1121
+ function require4(uint abs, bytes4 expected) internal pure {
1122
+ if (read4(abs) != expected) revert UnexpectedValue();
1123
+ }
1124
+
1125
+ /// @notice Require the eight bytes at an absolute calldata position to match `expected`.
1126
+ /// @dev DANGER: Unchecked calldata read. Values beyond calldata are zero-padded.
1127
+ /// @param abs Absolute calldata position.
1128
+ /// @param expected Expected eight-byte value.
1129
+ function require8(uint abs, bytes8 expected) internal pure {
1130
+ if (read8(abs) != expected) revert UnexpectedValue();
1131
+ }
1132
+
1133
+ /// @notice Require the sixteen bytes at an absolute calldata position to match `expected`.
1134
+ /// @dev DANGER: Unchecked calldata read. Values beyond calldata are zero-padded.
1135
+ /// @param abs Absolute calldata position.
1136
+ /// @param expected Expected sixteen-byte value.
1137
+ function require16(uint abs, bytes16 expected) internal pure {
1138
+ if (read16(abs) != expected) revert UnexpectedValue();
1139
+ }
1140
+
1037
1141
  /// @notice Require the word at an absolute calldata position to match `expected`.
1038
1142
  /// @dev DANGER: Unchecked calldata read. Values beyond calldata are zero-padded.
1039
1143
  /// @param abs Absolute calldata position.
@@ -1060,7 +1164,7 @@ library Blocks {
1060
1164
  /// @return a First payload word.
1061
1165
  /// @return end Absolute position after the block.
1062
1166
  function unpack32(uint abs, uint spec) internal pure returns (bytes32 a, uint end) {
1063
- (abs, end) = expectFixed(abs, spec, 32);
1167
+ (abs, end) = expectFixed(abs, Specs.key(spec), 32);
1064
1168
  a = read32(abs);
1065
1169
  }
1066
1170
 
@@ -1071,7 +1175,7 @@ library Blocks {
1071
1175
  /// @return b Second payload word.
1072
1176
  /// @return end Absolute position after the block.
1073
1177
  function unpack64(uint abs, uint spec) internal pure returns (bytes32 a, bytes32 b, uint end) {
1074
- (abs, end) = expectFixed(abs, spec, 64);
1178
+ (abs, end) = expectFixed(abs, Specs.key(spec), 64);
1075
1179
  assembly ("memory-safe") {
1076
1180
  a := calldataload(abs)
1077
1181
  b := calldataload(add(abs, 0x20))
@@ -1089,7 +1193,7 @@ library Blocks {
1089
1193
  uint abs,
1090
1194
  uint spec
1091
1195
  ) internal pure returns (bytes32 a, bytes32 b, bytes32 c, uint end) {
1092
- (abs, end) = expectFixed(abs, spec, 96);
1196
+ (abs, end) = expectFixed(abs, Specs.key(spec), 96);
1093
1197
  assembly ("memory-safe") {
1094
1198
  a := calldataload(abs)
1095
1199
  b := calldataload(add(abs, 0x20))
@@ -1109,7 +1213,7 @@ library Blocks {
1109
1213
  uint abs,
1110
1214
  uint spec
1111
1215
  ) internal pure returns (bytes32 a, bytes32 b, bytes32 c, bytes32 d, uint end) {
1112
- (abs, end) = expectFixed(abs, spec, 128);
1216
+ (abs, end) = expectFixed(abs, Specs.key(spec), 128);
1113
1217
  assembly ("memory-safe") {
1114
1218
  a := calldataload(abs)
1115
1219
  b := calldataload(add(abs, 0x20))
@@ -1131,7 +1235,7 @@ library Blocks {
1131
1235
  uint abs,
1132
1236
  uint spec
1133
1237
  ) internal pure returns (bytes32 a, bytes32 b, bytes32 c, bytes32 d, bytes32 e, uint end) {
1134
- (abs, end) = expectFixed(abs, spec, 160);
1238
+ (abs, end) = expectFixed(abs, Specs.key(spec), 160);
1135
1239
  assembly ("memory-safe") {
1136
1240
  a := calldataload(abs)
1137
1241
  b := calldataload(add(abs, 0x20))
@@ -1322,6 +1426,22 @@ library Blocks {
1322
1426
  }
1323
1427
  }
1324
1428
 
1429
+ /// @notice Decode a low-level fixed-width DEBT block at `abs`.
1430
+ /// @param abs Absolute block position.
1431
+ /// @return liability Decoded liability identifier.
1432
+ /// @return debt Decoded debt quantity.
1433
+ function unpackDebt(uint abs) internal pure returns (bytes32 liability, uint debt) {
1434
+ uint head;
1435
+ assembly ("memory-safe") {
1436
+ head := calldataload(abs)
1437
+ }
1438
+ if (head >> 192 != Specs.Debt >> 192) revert InvalidBlock();
1439
+ assembly ("memory-safe") {
1440
+ liability := calldataload(add(abs, 0x08))
1441
+ debt := calldataload(add(abs, 0x28))
1442
+ }
1443
+ }
1444
+
1325
1445
  /// @notice Decode a low-level fixed-width ACCOUNT_ASSET block at `abs`.
1326
1446
  /// @param abs Absolute block position.
1327
1447
  /// @return account Decoded account identifier.
@@ -1591,7 +1711,7 @@ library Blocks {
1591
1711
  uint abs
1592
1712
  ) internal pure returns (uint entity, bytes calldata stream, uint end) {
1593
1713
  uint limit;
1594
- (abs, limit) = expect(abs, Specs.Annotation);
1714
+ (abs, limit) = expectKey(abs, Keys.Annotation);
1595
1715
  assembly ("memory-safe") {
1596
1716
  entity := calldataload(abs)
1597
1717
  }
@@ -1609,7 +1729,7 @@ library Blocks {
1609
1729
  uint abs
1610
1730
  ) internal pure returns (bytes32 account, bytes calldata state, bytes calldata input, uint end) {
1611
1731
  uint limit;
1612
- (abs, limit) = expect(abs, Specs.Context);
1732
+ (abs, limit) = expectKey(abs, Keys.Context);
1613
1733
  assembly ("memory-safe") {
1614
1734
  account := calldataload(abs)
1615
1735
  }
@@ -1630,7 +1750,7 @@ library Blocks {
1630
1750
  uint abs
1631
1751
  ) internal pure returns (uint cmd, uint resources, bytes calldata input, uint end) {
1632
1752
  uint limit;
1633
- (abs, limit) = expect(abs, Specs.Step);
1753
+ (abs, limit) = expectKey(abs, Keys.Step);
1634
1754
  assembly ("memory-safe") {
1635
1755
  cmd := calldataload(abs)
1636
1756
  resources := calldataload(add(abs, 0x20))
@@ -1649,7 +1769,7 @@ library Blocks {
1649
1769
  uint abs
1650
1770
  ) internal pure returns (uint target, uint resources, bytes calldata payload, uint end) {
1651
1771
  uint limit;
1652
- (abs, limit) = expect(abs, Specs.Call);
1772
+ (abs, limit) = expectKey(abs, Keys.Call);
1653
1773
  assembly ("memory-safe") {
1654
1774
  target := calldataload(abs)
1655
1775
  resources := calldataload(add(abs, 0x20))
@@ -1668,7 +1788,7 @@ library Blocks {
1668
1788
  uint abs
1669
1789
  ) internal pure returns (uint portal, uint resources, bytes calldata input, uint end) {
1670
1790
  uint limit;
1671
- (abs, limit) = expect(abs, Specs.Relay);
1791
+ (abs, limit) = expectKey(abs, Keys.Relay);
1672
1792
  assembly ("memory-safe") {
1673
1793
  portal := calldataload(abs)
1674
1794
  resources := calldataload(add(abs, 0x20))
@@ -1687,7 +1807,7 @@ library Blocks {
1687
1807
  uint abs
1688
1808
  ) internal pure returns (uint portal, uint resources, bytes calldata payload, uint end) {
1689
1809
  uint limit;
1690
- (abs, limit) = expect(abs, Specs.Dispatch);
1810
+ (abs, limit) = expectKey(abs, Keys.Dispatch);
1691
1811
  assembly ("memory-safe") {
1692
1812
  portal := calldataload(abs)
1693
1813
  resources := calldataload(add(abs, 0x20))
@@ -1703,7 +1823,7 @@ library Blocks {
1703
1823
  /// @return end Absolute position after the block.
1704
1824
  function unpackLabel(uint abs) internal pure returns (bytes32 namespace, string memory name, uint end) {
1705
1825
  uint limit;
1706
- (abs, limit) = expect(abs, Specs.Label);
1826
+ (abs, limit) = expectKey(abs, Keys.Label);
1707
1827
  assembly ("memory-safe") {
1708
1828
  namespace := calldataload(abs)
1709
1829
  }
@@ -1721,7 +1841,7 @@ library Blocks {
1721
1841
  /// @return end Absolute position after the block.
1722
1842
  function unpackSchema(uint abs) internal pure returns (uint spec, string memory body, bytes32 name, uint end) {
1723
1843
  uint limit;
1724
- (abs, limit) = expect(abs, Specs.Schema);
1844
+ (abs, limit) = expectKey(abs, Keys.Schema);
1725
1845
  assembly ("memory-safe") {
1726
1846
  spec := calldataload(abs)
1727
1847
  }
@@ -1748,7 +1868,7 @@ library Blocks {
1748
1868
  uint abs
1749
1869
  ) internal pure returns (uint handler, uint resources, bytes32 key, bytes calldata witness, uint end) {
1750
1870
  uint limit;
1751
- (abs, limit) = expect(abs, Specs.Recover);
1871
+ (abs, limit) = expectKey(abs, Keys.Recover);
1752
1872
  assembly ("memory-safe") {
1753
1873
  handler := calldataload(abs)
1754
1874
  resources := calldataload(add(abs, 0x20))
@@ -1919,6 +2039,15 @@ library Blocks {
1919
2039
  writeBalance(value, 0, asset, amount);
1920
2040
  }
1921
2041
 
2042
+ /// @notice Encode a DEBT block.
2043
+ /// @param liability Liability identifier.
2044
+ /// @param debt Debt quantity.
2045
+ /// @return value Encoded DEBT block bytes.
2046
+ function createDebt(bytes32 liability, uint debt) internal pure returns (bytes memory value) {
2047
+ value = allocate(Sizes.Debt);
2048
+ writeDebt(value, 0, liability, debt);
2049
+ }
2050
+
1922
2051
  /// @notice Encode a CUSTODY block.
1923
2052
  /// @param host Host node ID holding the custody.
1924
2053
  /// @param asset Asset identifier.