@rootzero/contracts 1.25.0 → 1.26.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 (68) hide show
  1. package/CHANGELOG.md +101 -0
  2. package/Codec.sol +1 -2
  3. package/Endpoints.sol +2 -0
  4. package/README.md +61 -51
  5. package/Utils.sol +2 -1
  6. package/codec/Blocks.sol +276 -88
  7. package/codec/Buffers.sol +24 -20
  8. package/codec/Decoders.sol +35 -37
  9. package/codec/Descriptors.sol +75 -72
  10. package/codec/Keys.sol +5 -1
  11. package/codec/Schema.sol +5 -1
  12. package/codec/Specs.sol +19 -9
  13. package/codec/Writers.sol +14 -19
  14. package/commands/Allocate.sol +8 -7
  15. package/commands/Base.sol +14 -49
  16. package/commands/Bootstrap.sol +95 -0
  17. package/commands/Burn.sol +7 -7
  18. package/commands/Cashout.sol +85 -0
  19. package/commands/Credit.sol +18 -16
  20. package/commands/Debit.sol +23 -19
  21. package/commands/Deposit.sol +13 -13
  22. package/commands/Payout.sol +8 -8
  23. package/commands/Provision.sol +13 -13
  24. package/commands/Recover.sol +7 -7
  25. package/commands/Relay.sol +22 -23
  26. package/commands/Repay.sol +36 -34
  27. package/commands/Settle.sol +24 -22
  28. package/commands/Withdraw.sol +7 -7
  29. package/commands/admin/AllowAssets.sol +7 -7
  30. package/commands/admin/Allowance.sol +7 -7
  31. package/commands/admin/Annotate.sol +7 -7
  32. package/commands/admin/Appoint.sol +7 -7
  33. package/commands/admin/Authorize.sol +7 -7
  34. package/commands/admin/Base.sol +5 -8
  35. package/commands/admin/DenyAssets.sol +7 -7
  36. package/commands/admin/Dismiss.sol +7 -7
  37. package/commands/admin/Execute.sol +8 -8
  38. package/commands/admin/Unauthorize.sol +7 -7
  39. package/core/Calls.sol +3 -3
  40. package/core/Endpoint.sol +7 -7
  41. package/core/Pipeline.sol +19 -20
  42. package/execution/Budget.sol +2 -3
  43. package/execution/Execution.sol +271 -509
  44. package/guards/Base.sol +1 -1
  45. package/guards/Revoke.sol +7 -7
  46. package/package.json +1 -1
  47. package/ports/Allowance.sol +3 -3
  48. package/ports/Assets.sol +7 -7
  49. package/ports/Base.sol +1 -1
  50. package/ports/Credit.sol +3 -3
  51. package/ports/Debit.sol +3 -3
  52. package/ports/Dispatch.sol +3 -3
  53. package/ports/Pipe.sol +3 -3
  54. package/ports/Post.sol +3 -3
  55. package/ports/Redeem.sol +3 -3
  56. package/queries/Assets.sol +3 -3
  57. package/queries/Balances.sol +3 -3
  58. package/queries/Base.sol +1 -1
  59. package/utils/Accounts.sol +1 -3
  60. package/utils/Actions.sol +2 -0
  61. package/utils/Assets.sol +1 -10
  62. package/utils/Cursors.sol +60 -116
  63. package/utils/Errors.sol +50 -0
  64. package/utils/Ids.sol +2 -5
  65. package/utils/Lanes.sol +0 -2
  66. package/utils/Nodes.sol +1 -3
  67. package/utils/Utils.sol +2 -9
  68. package/codec/Readers.sol +0 -190
package/CHANGELOG.md CHANGED
@@ -8,6 +8,107 @@ sections are immutable and must continue to describe the tagged release.
8
8
 
9
9
  ## Unreleased
10
10
 
11
+ ## 1.26.0
12
+
13
+ ### Breaking Changes
14
+
15
+ - STEP blocks now encode `uint128 value` directly instead of a full-width
16
+ chain-specific `uint resources` word. The fixed STEP prefix is 16 bytes
17
+ smaller, and pipeline dispatch no longer truncates or interprets resource bits.
18
+ - Commands now return `(bytes state, uint credit)` instead of separate state
19
+ and transaction block streams. Pipelines trust the scalar return and add it
20
+ to the shared value budget, allowing later commands to spend it before the
21
+ enclosing entrypoint settles the final budget once. Transaction blocks remain
22
+ available for explicit posting through ports.
23
+ - Removed transaction writer metadata from endpoint descriptors and execution
24
+ writer lanes.
25
+ - Removed descriptor checks, eager lane scans, group reconciliation, and the
26
+ expected batch argument from execution opening. State/input keys and strides
27
+ remain descriptor metadata; command decoding and loops define their runtime
28
+ semantics, while finalization rejects unread state or input bytes.
29
+ - Removed the generic memory-backed `Reader` and `Readers` API from
30
+ `Codec.sol`. Fixed homogeneous memory streams now use absolute-position
31
+ `Memory` unpackers.
32
+ - Removed `Decoders.wrap` and the scanning `Decoders.batch` constructor.
33
+ `Decoders.open` now wraps any calldata source without initial checks,
34
+ including empty sources; explicit decoding defines structure and cardinality.
35
+ - Removed the unused `Cursors.pair`, `locate`, and `before` helpers. Cursor
36
+ pairs are packed directly where execution needs them.
37
+ - Replaced the cross-chain-ambiguous `#budget` block with the command-specific
38
+ `#cashout { uint amount }` block.
39
+
40
+ ### Added
41
+
42
+ - Added the standard
43
+ `#bootstrap { bytes32 asset, uint amount, uint budget }` input block and
44
+ `Bootstrap` command. It uses the standard `DebitAccountHook` for each initial
45
+ balance and a dedicated `BootstrapBudgetHook` for native-value contributions,
46
+ including zero contributions. `BootstrapInternal` provides direct local
47
+ pipeline dispatch without a self-call.
48
+ - Added `Cashout`, its dedicated native-withdrawal hook, optimized
49
+ `CashoutInternal` dispatch, and the canonical `Actions.Cashout` annotation.
50
+ Added `Actions.Cashin` as its native-deposit counterpart.
51
+ - Added `utils/Errors.sol` as the canonical declaration source for all
52
+ utility-layer errors, preserving their existing signatures and selectors.
53
+ - Added hint-only `Blocks.runCount`, a minimal assembly scan that counts complete
54
+ consecutive keyed blocks without treating the result as structural validation.
55
+ - Added `Executions.takeRawState` for forwarding an intact state lane while
56
+ marking it consumed.
57
+ - Added symmetric `Executions.takeRawInput` for forwarding and consuming an
58
+ intact input lane.
59
+ - Added `Decoders.close` to reject unread bytes explicitly. Execution `finish`
60
+ and `close` apply the same invariant automatically.
61
+ - Added `Execution.close(extraCredit)` to combine trusted command-produced
62
+ credit with the execution's remaining value budget during finalization.
63
+ - Added `Memory.bounds` and specialized `unpackBalance`, `unpackDebt`,
64
+ `unpackPosition`, and `unpackTransaction` helpers for fixed-stride memory
65
+ block streams.
66
+
67
+ ### Changed
68
+
69
+ - Replaced the unused checked `Cursors.wrap(source, flags, tag)` with the
70
+ two-argument packed calldata wrapper used by generic decoders. Removed the
71
+ duplicate private execution `openDecoder` implementation.
72
+ - Replaced the unused cursor item-count metadata with an optional one-byte
73
+ block stride. `Descriptors` now fully opens single or paired decoder cursors,
74
+ selects the active lane, derives the output allocation hint, and initializes
75
+ the writer cursor. Removed descriptor field accessors so packed layout
76
+ interpretation remains internal to the codec. Endpoint and command helpers
77
+ now construct `Execution` directly with the returned cursors and `msg.value`;
78
+ removed the descriptor-backed `Executions.open*` wrappers.
79
+ - Execution output writers now derive their initial capacity from an optimized
80
+ hint-only scan of the selected low decoder lane and grow as needed. Output
81
+ strides and size hints no longer impose a hard precomputed batch capacity.
82
+ - All buffers now grow beyond their initial capacity. Removed the `Growable`
83
+ cursor flag and the growth-policy booleans from buffer, writer, specification,
84
+ and descriptor allocation APIs.
85
+ - `Execution` now stores its sole output writer as a direct untagged cursor;
86
+ output reservation and finalization no longer perform lane selection.
87
+ - Execution traversal and unpack helpers now consume the active low decoder
88
+ lane without a lane argument. Mixed-lane commands select explicitly with
89
+ chainable `onstate()` and `oninput()` helpers. Paired executions use descriptor
90
+ metadata to place state low for state-only commands, which decode directly.
91
+ Relay commands select input explicitly before decoding their forwarded block.
92
+ - `Execution.close()` now finalizes output and drains the remaining budget as
93
+ `(bytes output, uint credit)`; commands return it directly without the former
94
+ `CommandBase.closeCommand` wrapper. Budget draining is inlined in `close()`.
95
+ - Internal debit processing now validates its fixed calldata stride once and
96
+ decodes each amount directly. Internal credit, settlement, repayment, and
97
+ pipeline transaction processing do the equivalent for memory streams.
98
+ - Pipeline step value is now checked and deducted directly from the scalar
99
+ budget, avoiding the generic budget-helper call in the dispatch loop. The
100
+ loop now also traverses absolute calldata bounds and unpacks STEP blocks
101
+ directly without allocating a decoder cursor.
102
+ - `Cursors` now performs packed construction, bounds, navigation, selection,
103
+ and consumption directly in each helper, avoiding nested internal-call
104
+ overhead across low-level decoding paths.
105
+ - `Blocks.expectKey` and the specialized dynamic leaf unpackers now validate
106
+ their headers directly, reducing composite decoding overhead.
107
+ - `Specs` and `Descriptors` now calculate hot-path counts and writer allocation
108
+ directly from their packed fields.
109
+ - Buffer reservation/finalization, fixed-size writer reservation, and execution
110
+ opening/output paths now operate directly on validated packed cursors.
111
+
11
112
  ## 1.25.0
12
113
 
13
114
  ### Breaking Changes
package/Codec.sol CHANGED
@@ -11,8 +11,7 @@ import { Descriptors, Flags } from "./codec/Descriptors.sol";
11
11
  import { Schemas } from "./codec/Schema.sol";
12
12
  import { Decoders } from "./codec/Decoders.sol";
13
13
  import { Cursors, Cur } from "./utils/Cursors.sol";
14
- import { Readers, Reader } from "./codec/Readers.sol";
15
- import { Blocks } from "./codec/Blocks.sol";
14
+ import { Blocks, Memory } from "./codec/Blocks.sol";
16
15
  import { Buffers } from "./codec/Buffers.sol";
17
16
  import { Writer, Writers } from "./codec/Writers.sol";
18
17
 
package/Endpoints.sol CHANGED
@@ -13,6 +13,8 @@ import {CreditAccountHook, DebitAccountHook, PostHook, RepayHook, SettleHook} fr
13
13
  import {CommandBase} from "./commands/Base.sol";
14
14
  import {Allocate, AllocateHook} from "./commands/Allocate.sol";
15
15
  import {Burn, BurnHook} from "./commands/Burn.sol";
16
+ import {Bootstrap, BootstrapBudgetHook, BootstrapInternal} from "./commands/Bootstrap.sol";
17
+ import {Cashout, CashoutHook, CashoutInternal} from "./commands/Cashout.sol";
16
18
  import {CreditAccount, CreditAccountInternal} from "./commands/Credit.sol";
17
19
  import {DebitAccount, DebitAccountInternal} from "./commands/Debit.sol";
18
20
  import {Deposit, DepositHook, DepositPayable, DepositPayableHook} from "./commands/Deposit.sol";
package/README.md CHANGED
@@ -135,8 +135,8 @@ A input is not a single struct; it is a run of blocks. One `#amount` block
135
135
  asks for one deposit, five blocks ask for five, and the code path is identical
136
136
  — every endpoint parses with a cursor and loops until the stream is exhausted.
137
137
  The descriptor lane key is the prime item: it is the block type that may repeat
138
- for batching. Readers interpret a zero group byte as group size 1 when the lane
139
- is non-empty.
138
+ for batching. Descriptor decoding interprets a zero group byte as group size 1
139
+ when the lane is non-empty.
140
140
 
141
141
  Off-chain, building a batch is concatenation. Using the reference encoders from
142
142
  [`test/helpers/blocks.ts`](test/helpers/blocks.ts):
@@ -149,13 +149,12 @@ const input = concat([
149
149
  encodeAmountBlock(usdc, 250_000_000n),
150
150
  encodeAmountBlock(dai, 250n * 10n ** 18n),
151
151
  ]);
152
- // deposit(input) returns two #balance blocks in its state output and an
153
- // empty transaction output
152
+ // deposit(input) returns two #balance blocks and zero native budget credit
154
153
  ```
155
154
 
156
155
  Everything downstream keeps this shape: commands loop over input blocks,
157
- posting loops over transactions, pipelines loop over steps. Batching is
158
- never a special case.
156
+ posting ports loop over transactions, and pipelines loop over steps. Batching
157
+ is never a special case.
159
158
 
160
159
  ## IDs, Accounts, Assets, and Nodes
161
160
 
@@ -254,17 +253,19 @@ struct CommandContext {
254
253
  }
255
254
  ```
256
255
 
257
- Every command returns two block streams: `state`, which is threaded into the
258
- next pipeline step, and `transactions`, which contains `#transaction` blocks
259
- for the pipeline host to post outside the state lane. Either stream may be
260
- empty.
256
+ Every command returns a `state` block stream and a trusted native `credit`.
257
+ State is threaded into the next pipeline step, while credit replenishes the
258
+ shared pipeline budget without validation against forwarded call value.
259
+ Command trust is the authority boundary.
261
260
 
262
261
  State is linear, not optional ambient context. A command is responsible for
263
262
  the entire state stream it receives: it must validate and consume it, transform
264
263
  and return it, forward it intact, or revert. A command must never succeed while
265
- silently ignoring or dropping supplied state. Commands that declare
266
- `Specs.Empty` state therefore reject any non-empty state, while commands that
267
- accept state validate the complete stream against their declared state schema.
264
+ silently ignoring or dropping supplied state. Descriptor schemas remain
265
+ discovery metadata; the command's decoding and loop implementation defines its
266
+ runtime lane semantics. A command that does not consume supplied state rejects
267
+ it when closing, while `takeRawState` explicitly consumes an intact forwarded
268
+ state lane.
268
269
  This is especially important for `#debt` and `#position`, because dropping
269
270
  either could silently discard an outstanding debt requirement.
270
271
 
@@ -285,22 +286,22 @@ claims, fees, netting, and other multi-step operations. Debt and position are
285
286
  transient representations and do not themselves create or erase an obligation
286
287
  recorded by an external system.
287
288
 
288
- The standard `Deposit` mixin shows the canonical shape: open and validate both
289
- command lanes, loop the batch, call the hook, and write the output run:
289
+ The standard `Deposit` mixin shows the canonical shape: open the execution,
290
+ decode its active input lane, call the hook, and write the output run:
290
291
 
291
292
  ```solidity
292
293
  function deposit(
293
294
  bytes calldata context
294
- ) external onlyCommand returns (bytes memory, bytes memory) {
295
- Execution memory exec = openCommand(context, descriptor, 0);
295
+ ) external onlyCommand returns (bytes memory, uint) {
296
+ Execution memory exec = openCommand(context, descriptor);
296
297
 
297
298
  while (exec.more()) {
298
- (bytes32 asset, uint amount) = exec.unpackAmount(Lanes.Input);
299
+ (bytes32 asset, uint amount) = exec.unpackAmount();
299
300
  deposit(exec.account, asset, amount); // host policy hook
300
301
  exec.outputBalance(asset, amount);
301
302
  }
302
303
 
303
- return closeCommand(exec);
304
+ return exec.close();
304
305
  }
305
306
  ```
306
307
 
@@ -313,19 +314,19 @@ abstract contract MyCommand is CommandBase {
313
314
  uint private immutable descriptor;
314
315
 
315
316
  constructor() {
316
- (, descriptor) = command("myCommand", Specs.Empty, Specs.Amount, Specs.Balance, 0, 0);
317
+ (, descriptor) = command("myCommand", Specs.Empty, Specs.Amount, Specs.Balance, 0);
317
318
  }
318
319
 
319
320
  function myCommand(
320
321
  bytes calldata context
321
- ) external onlyCommand returns (bytes memory, bytes memory) {
322
- Execution memory exec = openCommand(context, descriptor, 0);
322
+ ) external onlyCommand returns (bytes memory, uint) {
323
+ Execution memory exec = openCommand(context, descriptor);
323
324
  while (exec.more()) {
324
- (bytes32 asset, uint amount) = exec.unpackAmount(Lanes.Input);
325
+ (bytes32 asset, uint amount) = exec.unpackAmount();
325
326
  // Apply command-specific behavior for this group.
326
327
  exec.outputBalance(asset, amount);
327
328
  }
328
- return closeCommand(exec);
329
+ return exec.close();
329
330
  }
330
331
  }
331
332
  ```
@@ -336,7 +337,9 @@ or compose values such as `Flags.Funded`, `Flags.Admin`, and
336
337
  and 7 are reserved for endpoint-defined custom flags. Bits 2 through 5 remain
337
338
  reserved for future protocol flags.
338
339
 
339
- The standard commands cover the common ledger movements: `deposit` and
340
+ The standard commands cover the common ledger movements: `bootstrap` (source
341
+ an initial balance and native-value budget), `cashout` (withdraw a requested
342
+ native-asset amount), `deposit` and
340
343
  `depositPayable` (external funds in), `settlePayable` (funded settlement),
341
344
  `withdraw` and `burn` (funds out),
342
345
  `debitAccount` and `creditAccount` (internal movements), `payout` (deliver
@@ -354,53 +357,60 @@ A single command is rarely the whole story. A pipeline is a run of `#step`
354
357
  blocks executed in order within one transaction:
355
358
 
356
359
  ```txt
357
- step { uint cmd, uint resources, #bytes as input }
360
+ step { uint cmd, uint128 value, #bytes as input }
358
361
  ```
359
362
 
360
- Each step names a command, the resources it may spend, and its input.
363
+ Each step names a command, the native value it may spend, and its input.
361
364
  The returned state threads into the next command and the final state must be
362
- empty. Returned transactions do not enter the state lane; the pipeline passes
363
- each decoded transaction to the shared posting implementation before
364
- running the next step. This is the core of `Pipeline.pipe`:
365
+ empty. Each returned native credit replenishes the budget before running the
366
+ next step, allowing one command to fund later commands. The standard
367
+ `bootstrap` command consumes a stream of
368
+ `#bootstrap { bytes32 asset, uint amount, uint budget }` requests and atomically
369
+ debits each asset through the standard account hook, introduces matching
370
+ `#balance` state, and sources summed trusted credit through its dedicated budget
371
+ hook. This is the core of
372
+ `Pipeline.pipe`:
365
373
 
366
374
  ```solidity
367
375
  while (cur.more()) {
368
- (uint cmd, uint resources, bytes calldata input) = cur.unpackStep();
369
- uint128 value;
370
- (budget, value) = Budgets.useResourceValue(budget, resources);
371
- Reader memory transactions;
372
- (state, transactions.source) = dispatch(
376
+ (uint cmd, uint128 value, bytes calldata input) = cur.unpackStep();
377
+ if (budget < value) revert InsufficientValue();
378
+ unchecked { budget -= value; }
379
+ uint credit;
380
+ (state, credit) = dispatch(
373
381
  cmd,
374
382
  account,
375
383
  state,
376
384
  input,
377
385
  value
378
386
  );
379
- while (transactions.more()) {
380
- (bytes32 from, bytes32 to, bytes32 asset, uint amount) = transactions.unpackTransaction();
381
- post(from, to, asset, amount);
382
- }
387
+ budget += credit;
383
388
  }
384
389
  if (state.length != 0) revert UnexpectedState();
385
390
  ```
386
391
 
387
392
  `Pipeline.pipe` takes the available native-value budget as a `uint` and returns
388
- the remaining budget after every step has executed.
393
+ the remaining budget after every step has executed. The enclosing entrypoint
394
+ settles that final value once.
389
395
 
390
396
  A transfer, for instance, is a two-step pipeline: `debitAccount` turns an
391
397
  `#amount` input into `#balance` state, and `payout` consumes that state
392
398
  toward a recipient. Because a pipeline is just blocks, it is also the unit of
393
- command batching and `resources` is a chain-specific word interpreted by the
394
- portal adapter (on EVM, the low 128 bits are native value in wei, drawn from a
395
- shared budget), so the same pipeline bytes are meaningful to every port.
396
-
397
- Hosts that implement a pipeline locally can inherit `DebitAccountInternal`,
398
- `CreditAccountInternal`, `SettleInternal`, and `RepayInternal` to advertise the
399
- canonical command endpoints while routing their local command IDs through
399
+ command batching. A step's `uint128 value` is drawn directly from the shared
400
+ native-value budget. Transport envelopes retain separate chain-specific
401
+ `resources` fields for adapters that also need gas or runtime parameters.
402
+
403
+ Hosts that implement a pipeline locally can inherit `BootstrapInternal`,
404
+ `CashoutInternal`, `DebitAccountInternal`, `CreditAccountInternal`,
405
+ `SettleInternal`, and
406
+ `RepayInternal` to advertise the canonical command endpoints while routing
407
+ their local command IDs through `executeBootstrap`, `executeCashout`,
400
408
  `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.
409
+ `executeRepay`. The bootstrap, cashout, and debit adapters decode fixed-stride
410
+ calldata input directly; the other three
411
+ decode memory-backed pipeline state. All avoid an external self-call. Pass the
412
+ step value into each adapter; all six reject nonzero value because the commands
413
+ are non-funded.
404
414
 
405
415
  Positions also support backward-composed pipelines. In an exact-output route,
406
416
  the asset side can represent the desired result while the liability side
@@ -493,7 +503,7 @@ Import from the package entry points rather than deep paths:
493
503
  - `@rootzero/contracts/Endpoints.sol` — command, admin, port, guard, and query
494
504
  mixins, their hooks (including `PipeHook`), and `Flags`
495
505
  - `@rootzero/contracts/Codec.sol` — `Blocks`, calldata `Cur`/`Cursors`, memory
496
- `Reader`/`Readers`, `Writers`, `Schemas`, `Descriptors`, `Flags`, `Keys`, and
506
+ `Memory`, `Writers`, `Schemas`, `Descriptors`, `Flags`, `Keys`, and
497
507
  `Specs`
498
508
  - `@rootzero/contracts/Utils.sol` — `Ids`, `Nodes`, `Assets`, `Accounts`,
499
509
  layout and value helpers
package/Utils.sol CHANGED
@@ -12,7 +12,8 @@ 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, 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";
15
+ import { BadAmount, InsufficientValue, InvalidAccount, InvalidAsset, InvalidContract, InvalidId, InvalidPreimage, MissingCursor, NotDivisible, OutOfBounds, UnauthorizedAsset, UnconsumedData, UnexpectedPosition, ValueOverflow, ZeroAddress, ZeroAmount} from "./utils/Errors.sol";
16
+ import { addrOr, applyBps, beforeBps, bytes32ToInt, bytes32ToString, clear8, clear16, clear32, clear64, divisible, ensureAddr, ensureContract, hash32, intToBytes32, isFamily, matchesBase, MAX_BPS, max8, max16, max24, max32, max40, max64, max96, max128, max160, replace8, replace16, replace32, replace64, retryTicket, toLocalBase, toUnspecifiedBase } from "./utils/Utils.sol";
16
17
 
17
18
 
18
19