@rootzero/contracts 1.26.0 → 1.27.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
@@ -8,6 +8,30 @@ sections are immutable and must continue to describe the tagged release.
8
8
 
9
9
  ## Unreleased
10
10
 
11
+ ## 1.27.0
12
+
13
+ ### Breaking Changes
14
+
15
+ - Removed `BootstrapBudgetHook`. Bootstrap budget contributions now debit the
16
+ account's local native asset through the standard `DebitAccountHook`.
17
+ - Removed the external `bootstrap` endpoint and `BootstrapInternal`. Bootstrap
18
+ is now a pipeline-local command implemented directly by `Bootstrap` while
19
+ retaining its registered command ID and descriptor metadata.
20
+ - Replaced the codec-specific `Executions.ZeroStride` error with global
21
+ `UnexpectedState` and `UnexpectedInput` errors for pipeline-local lane
22
+ violations.
23
+
24
+ ### Changed
25
+
26
+ - Node authorization and revocation now reject foreign-chain and opaque node
27
+ IDs, keeping the trusted-node set local to the host chain.
28
+ - Asserting EVM, admin, and user account helpers now require a nonzero embedded
29
+ address while continuing to return the original account ID.
30
+ - Documented that either side of a position may be absent, using a zero
31
+ identifier and quantity like an omitted transaction endpoint.
32
+ - Bootstrap uses assigned step value before debiting any remaining native-asset
33
+ amount from the account and returns unused value as credit.
34
+
11
35
  ## 1.26.0
12
36
 
13
37
  ### Breaking Changes
package/Endpoints.sol CHANGED
@@ -13,7 +13,7 @@ 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";
16
+ import {Bootstrap} from "./commands/Bootstrap.sol";
17
17
  import {Cashout, CashoutHook, CashoutInternal} from "./commands/Cashout.sol";
18
18
  import {CreditAccount, CreditAccountInternal} from "./commands/Credit.sol";
19
19
  import {DebitAccount, DebitAccountInternal} from "./commands/Debit.sol";
package/README.md CHANGED
@@ -276,6 +276,14 @@ next. Balance carries `{ asset, amount }`, debt carries `{ liability, debt }`,
276
276
  and position carries their flat combination
277
277
  `{ asset, amount, liability, debt }`.
278
278
 
279
+ Either side of a position may be absent. An absent asset side is encoded as
280
+ `asset = 0, amount = 0`; an absent liability side is encoded as
281
+ `liability = 0, debt = 0`. This mirrors transaction blocks, where a zero `from`
282
+ or `to` omits that side of the transfer. A one-sided position remains useful
283
+ when a command must preserve position-shaped state for later composition;
284
+ otherwise the narrower `#balance` or `#debt` block expresses the same live
285
+ value more directly.
286
+
279
287
  `#debt` and `#position` are general live state rather than persisted
280
288
  lending-specific debt records. Debt carries value owed or required; position
281
289
  pairs that liability with value acquired or controlled. A command may preserve
@@ -367,8 +375,11 @@ next step, allowing one command to fund later commands. The standard
367
375
  `bootstrap` command consumes a stream of
368
376
  `#bootstrap { bytes32 asset, uint amount, uint budget }` requests and atomically
369
377
  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
378
+ `#balance` state, and debits each nonzero budget contribution from the account's
379
+ native asset through the same hook. Its pipeline-local implementation uses assigned step value first when
380
+ bootstrapping the native asset, debits any remainder from the account, and
381
+ returns unused assigned value as credit. Bootstrap is registered with command
382
+ metadata but is only executable through local pipeline dispatch. This is the core of
372
383
  `Pipeline.pipe`:
373
384
 
374
385
  ```solidity
@@ -400,17 +411,18 @@ command batching. A step's `uint128 value` is drawn directly from the shared
400
411
  native-value budget. Transport envelopes retain separate chain-specific
401
412
  `resources` fields for adapters that also need gas or runtime parameters.
402
413
 
403
- Hosts that implement a pipeline locally can inherit `BootstrapInternal`,
414
+ Hosts that implement a pipeline locally can inherit `Bootstrap`,
404
415
  `CashoutInternal`, `DebitAccountInternal`, `CreditAccountInternal`,
405
416
  `SettleInternal`, and
406
- `RepayInternal` to advertise the canonical command endpoints while routing
417
+ `RepayInternal` to register canonical command metadata while routing
407
418
  their local command IDs through `executeBootstrap`, `executeCashout`,
408
419
  `executeDebitAccount`, `executeCreditAccount`, `executeSettle`, and
409
420
  `executeRepay`. The bootstrap, cashout, and debit adapters decode fixed-stride
410
421
  calldata input directly; the other three
411
422
  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.
423
+ step value into each adapter. Bootstrap is pipeline-local rather than an
424
+ externally callable command and may consume value for native-asset balance;
425
+ the other five reject nonzero value because those commands are non-funded.
414
426
 
415
427
  Positions also support backward-composed pipelines. In an exact-output route,
416
428
  the asset side can represent the desired result while the liability side
package/codec/Schema.sol CHANGED
@@ -60,6 +60,8 @@ pragma solidity ^0.8.33;
60
60
  // - while a balance, debt, or custody is in-flight as pipeline state, it is not simultaneously persisted
61
61
  // in another ledger/store by this protocol
62
62
  // - debt carries only a live liability side; position pairs live balance and debt sides
63
+ // - either position side may be absent by setting both its identifier and quantity to zero,
64
+ // analogous to omitting a transaction side with a zero `from` or `to`
63
65
  // - debt and position state are transient and do not themselves create or erase an externally persisted obligation
64
66
  // - positions support backward composition, but pipeline steps always execute in encoded order
65
67
  // - commands must preserve, transform, settle, or intentionally consume pipeline state
@@ -1,33 +1,20 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import {Execution, Executions, CommandBase, Specs} from "./Base.sol";
4
+ import {CommandBase, Specs} from "./Base.sol";
5
5
  import {Blocks} from "../codec/Blocks.sol";
6
6
  import {Sizes} from "../codec/Specs.sol";
7
7
  import {Cursors} from "../utils/Cursors.sol";
8
8
  import {DebitAccountHook} from "../core/Settlement.sol";
9
-
10
- using Executions for Execution;
11
-
12
- /// @notice Hook implemented by hosts that source bootstrap pipeline budget.
13
- abstract contract BootstrapBudgetHook {
14
- /// @notice Source native-value budget for `account` during bootstrap.
15
- /// Called once per BOOTSTRAP input block.
16
- /// @dev Implementations must revert unless the contribution can be made available.
17
- /// A zero amount is valid and may return immediately without changing state.
18
- /// @param account Account funding the pipeline.
19
- /// @param amount Native value added to the pipeline budget.
20
- function bootstrapBudget(bytes32 account, uint amount) internal virtual;
21
- }
9
+ import {UnexpectedState} from "../utils/Errors.sol";
22
10
 
23
11
  /// @title Bootstrap
24
- /// @notice Command that atomically starts a pipeline with BALANCE state and native-value budget.
25
- abstract contract Bootstrap is CommandBase, DebitAccountHook, BootstrapBudgetHook {
26
- uint private immutable descriptor;
12
+ /// @notice Pipeline-local command that atomically starts with BALANCE state and native-value budget.
13
+ abstract contract Bootstrap is CommandBase, DebitAccountHook {
27
14
  uint private immutable id;
28
15
 
29
16
  constructor() {
30
- (id, descriptor) = command("bootstrap", Specs.Empty, Specs.Bootstrap, Specs.Balance, 0);
17
+ (id,) = command("bootstrap", Specs.Empty, Specs.Bootstrap, Specs.Balance, 0);
31
18
  }
32
19
 
33
20
  /// @notice Return the registered BOOTSTRAP command ID.
@@ -35,57 +22,56 @@ abstract contract Bootstrap is CommandBase, DebitAccountHook, BootstrapBudgetHoo
35
22
  return id;
36
23
  }
37
24
 
38
- /// @notice Source initial balances and budget contributions for a pipeline.
39
- /// @param context Command context carrying a BOOTSTRAP input stream.
40
- /// @return output One BALANCE block per BOOTSTRAP input.
41
- /// @return credit Sum of trusted native value contributed by every input.
25
+ /// @dev Bootstrap one local balance, using assigned value before debiting
26
+ /// any remaining native-asset amount from the account.
42
27
  function bootstrap(
43
- bytes calldata context
44
- ) external onlyCommand returns (bytes memory output, uint credit) {
45
- Execution memory exec = openCommand(context, descriptor);
46
-
47
- while (exec.more()) {
48
- (bytes32 asset, uint amount, uint budget) = exec.unpackBootstrap();
49
- debitAccount(exec.account, asset, amount);
50
- bootstrapBudget(exec.account, budget);
51
- exec.outputBalance(asset, amount);
52
- credit += budget;
28
+ bytes32 account,
29
+ bytes32 asset,
30
+ uint amount,
31
+ uint budget,
32
+ uint value
33
+ ) private returns (uint) {
34
+ if (asset == nativeAsset) {
35
+ uint funded = amount < value ? amount : value;
36
+ unchecked {
37
+ amount -= funded;
38
+ value -= funded;
39
+ }
40
+ amount += budget;
41
+ } else {
42
+ if (amount != 0) debitAccount(account, asset, amount);
43
+ amount = budget;
53
44
  }
54
45
 
55
- return exec.close(credit);
46
+ if (amount != 0) debitAccount(account, nativeAsset, amount);
47
+ return value + budget;
56
48
  }
57
- }
58
49
 
59
- /// @title BootstrapInternal
60
- /// @notice Extends bootstrap with optimized local pipeline dispatch.
61
- abstract contract BootstrapInternal is Bootstrap {
62
50
  /// @notice Execute bootstrap directly against a calldata BOOTSTRAP stream.
63
51
  /// @param account Account funding the pipeline.
64
52
  /// @param state Empty pipeline state required by the command schema.
65
53
  /// @param input BOOTSTRAP block stream.
66
- /// @param value Native value assigned to this command; must be zero.
54
+ /// @param value Native value available to fund native-asset balances.
67
55
  /// @return output One BALANCE block per BOOTSTRAP input.
68
- /// @return credit Sum of the sourced budget contributions.
56
+ /// @return credit Sourced budget contributions plus unused assigned value.
69
57
  function executeBootstrap(
70
58
  bytes32 account,
71
59
  bytes memory state,
72
60
  bytes calldata input,
73
61
  uint128 value
74
62
  ) internal returns (bytes memory output, uint credit) {
75
- if (value != 0) revert ValueNotAllowed();
76
- if (state.length != 0) revert Executions.ZeroStride();
63
+ if (state.length != 0) revert UnexpectedState();
77
64
  if (input.length % Sizes.Bootstrap != 0) revert Blocks.InvalidBlock();
78
65
 
79
66
  (uint abs, uint end) = Cursors.bounds(input);
80
67
  output = new bytes(input.length / Sizes.Bootstrap * Sizes.Balance);
68
+ credit = value;
81
69
  uint i;
82
70
 
83
71
  while (abs < end) {
84
72
  (bytes32 asset, uint amount, uint budget) = Blocks.unpackBootstrap(abs);
85
- debitAccount(account, asset, amount);
86
- bootstrapBudget(account, budget);
73
+ credit = bootstrap(account, asset, amount, budget, credit);
87
74
  Blocks.writeBalance(output, i, asset, amount);
88
- credit += budget;
89
75
  unchecked {
90
76
  abs += Sizes.Bootstrap;
91
77
  i += Sizes.Balance;
@@ -7,6 +7,7 @@ import {Sizes} from "../codec/Specs.sol";
7
7
  import {Cursors} from "../utils/Cursors.sol";
8
8
  import {Action} from "../annotations/Action.sol";
9
9
  import {Actions} from "../utils/Actions.sol";
10
+ import {UnexpectedState} from "../utils/Errors.sol";
10
11
 
11
12
  using Executions for Execution;
12
13
 
@@ -70,7 +71,7 @@ abstract contract CashoutInternal is Cashout {
70
71
  uint128 value
71
72
  ) internal returns (bytes memory, uint) {
72
73
  if (value != 0) revert ValueNotAllowed();
73
- if (state.length != 0) revert Executions.ZeroStride();
74
+ if (state.length != 0) revert UnexpectedState();
74
75
  if (input.length % Sizes.Cashout != 0) revert Blocks.InvalidBlock();
75
76
 
76
77
  (uint abs, uint end) = Cursors.bounds(input);
@@ -5,6 +5,7 @@ import {Execution, Executions, CommandBase, Specs} from "./Base.sol";
5
5
  import {CreditAccountHook} from "../core/Settlement.sol";
6
6
  import {Blocks, Memory} from "../codec/Blocks.sol";
7
7
  import {Sizes} from "../codec/Specs.sol";
8
+ import {UnexpectedInput} from "../utils/Errors.sol";
8
9
 
9
10
  using Executions for Execution;
10
11
 
@@ -61,7 +62,7 @@ abstract contract CreditAccountInternal is CreditAccount {
61
62
  uint128 value
62
63
  ) internal returns (bytes memory, uint) {
63
64
  if (value != 0) revert ValueNotAllowed();
64
- if (input.length != 0) revert Executions.ZeroStride();
65
+ if (input.length != 0) revert UnexpectedInput();
65
66
  if (state.length == 0) revert Blocks.EmptyRun();
66
67
 
67
68
  (uint abs, uint end) = Memory.bounds(state, Sizes.Balance);
@@ -6,6 +6,7 @@ import {DebitAccountHook} from "../core/Settlement.sol";
6
6
  import {Blocks} from "../codec/Blocks.sol";
7
7
  import {Sizes} from "../codec/Specs.sol";
8
8
  import {Cursors} from "../utils/Cursors.sol";
9
+ import {UnexpectedState} from "../utils/Errors.sol";
9
10
 
10
11
  using Executions for Execution;
11
12
 
@@ -64,7 +65,7 @@ abstract contract DebitAccountInternal is DebitAccount {
64
65
  uint128 value
65
66
  ) internal returns (bytes memory, uint) {
66
67
  if (value != 0) revert ValueNotAllowed();
67
- if (state.length != 0) revert Executions.ZeroStride();
68
+ if (state.length != 0) revert UnexpectedState();
68
69
  if (input.length == 0) revert Blocks.EmptyRun();
69
70
  if (input.length % Sizes.Amount != 0) revert Blocks.InvalidBlock();
70
71
 
@@ -7,6 +7,7 @@ import {Action} from "../annotations/Action.sol";
7
7
  import {Actions} from "../utils/Actions.sol";
8
8
  import {Blocks, Memory} from "../codec/Blocks.sol";
9
9
  import {Sizes} from "../codec/Specs.sol";
10
+ import {UnexpectedInput} from "../utils/Errors.sol";
10
11
 
11
12
  using Executions for Execution;
12
13
 
@@ -164,7 +165,7 @@ abstract contract RepayInternal is Repay {
164
165
  uint128 value
165
166
  ) internal returns (bytes memory, uint) {
166
167
  if (value != 0) revert ValueNotAllowed();
167
- if (input.length != 0) revert Executions.ZeroStride();
168
+ if (input.length != 0) revert UnexpectedInput();
168
169
  if (state.length == 0) revert Blocks.EmptyRun();
169
170
 
170
171
  (uint abs, uint end) = Memory.bounds(state, Sizes.Debt);
@@ -7,6 +7,7 @@ import {Action} from "../annotations/Action.sol";
7
7
  import {Actions} from "../utils/Actions.sol";
8
8
  import {Blocks, Memory} from "../codec/Blocks.sol";
9
9
  import {Sizes} from "../codec/Specs.sol";
10
+ import {UnexpectedInput} from "../utils/Errors.sol";
10
11
 
11
12
  using Executions for Execution;
12
13
 
@@ -111,7 +112,7 @@ abstract contract SettleInternal is Settle {
111
112
  uint128 value
112
113
  ) internal returns (bytes memory, uint) {
113
114
  if (value != 0) revert ValueNotAllowed();
114
- if (input.length != 0) revert Executions.ZeroStride();
115
+ if (input.length != 0) revert UnexpectedInput();
115
116
  if (state.length == 0) revert Blocks.EmptyRun();
116
117
 
117
118
  (uint abs, uint end) = Memory.bounds(state, Sizes.Position);
package/core/Access.sol CHANGED
@@ -89,6 +89,7 @@ abstract contract NodeAccess is AdminAccess, TrustAccess, NodeEvent {
89
89
  /// @param node Node ID to update.
90
90
  /// @param active True to authorize the node, false to revoke it.
91
91
  function setNode(uint node, bool active) internal {
92
+ node = Nodes.local(node);
92
93
  nodes[node] = active;
93
94
  emit Node(host, node, active);
94
95
  }
package/core/Pipeline.sol CHANGED
@@ -3,7 +3,7 @@ pragma solidity ^0.8.33;
3
3
 
4
4
  import {Blocks} from "../codec/Blocks.sol";
5
5
  import {Cursors} from "../utils/Cursors.sol";
6
- import {InsufficientValue, OutOfBounds} from "../utils/Errors.sol";
6
+ import {InsufficientValue, OutOfBounds, UnexpectedState} from "../utils/Errors.sol";
7
7
 
8
8
  /// @notice Hook implemented by hosts that execute encoded step streams.
9
9
  abstract contract PipeHook {
@@ -19,9 +19,6 @@ abstract contract PipeHook {
19
19
  /// @title Pipeline
20
20
  /// @notice Core pipeline functionality shared by higher-level surfaces.
21
21
  abstract contract Pipeline is PipeHook {
22
- /// @dev Thrown when the pipeline finishes with non-empty threaded state.
23
- error UnexpectedState();
24
-
25
22
  /// @notice Override to dispatch one piped step.
26
23
  /// Called once per STEP block. The returned state becomes the state passed to
27
24
  /// the next step, and the final returned state must be empty. Returned
@@ -61,7 +61,8 @@ abstract contract Settlement is PostHook, SettleHook, RepayHook, DebitAccountHoo
61
61
  }
62
62
 
63
63
  /// @notice Settle one position by crediting its asset and debiting its liability.
64
- /// Skips either operation when its corresponding amount is zero.
64
+ /// Skips either operation when its corresponding amount is zero, including
65
+ /// position sides encoded as absent with a zero identifier and quantity.
65
66
  function settle(
66
67
  bytes32 account,
67
68
  bytes32 asset,
package/core/Types.sol CHANGED
@@ -76,6 +76,7 @@ struct Debt {
76
76
  }
77
77
 
78
78
  /// @notice Asset and liability pair threaded as live pipeline state.
79
+ /// Either side may be absent by setting both its identifier and quantity to zero.
79
80
  struct Position {
80
81
  /// @dev Identifier for the asset side.
81
82
  bytes32 asset;
@@ -28,9 +28,6 @@ struct Execution {
28
28
  library Executions {
29
29
  using Cursors for uint;
30
30
 
31
- /// @dev Command-defined lane policy rejected data in an expected-empty lane.
32
- error ZeroStride();
33
-
34
31
  // -------------------------------------------------------------------------
35
32
  // Opening
36
33
  // -------------------------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rootzero/contracts",
3
- "version": "1.26.0",
3
+ "version": "1.27.0",
4
4
  "description": "Solidity contracts and protocol building blocks for rootzero hosts and commands.",
5
5
  "private": false,
6
6
  "license": "GPL-3.0-only",
@@ -34,6 +34,7 @@ library Accounts {
34
34
  }
35
35
 
36
36
  /// @notice Return true if `account` belongs to the EVM account family.
37
+ /// @dev Classification only; does not require a nonzero embedded address.
37
38
  function isEvm(bytes32 account) internal pure returns (bool) {
38
39
  return isFamily(uint(account), Family);
39
40
  }
@@ -44,20 +45,24 @@ library Accounts {
44
45
  }
45
46
 
46
47
  /// @notice Return true if `account` is an admin account.
48
+ /// @dev Classification only; does not require a nonzero embedded address.
47
49
  function isAdmin(bytes32 account) internal pure returns (bool) {
48
50
  return prefix(account) == Admin;
49
51
  }
50
52
 
51
53
  /// @notice Return true if `account` is a user account.
54
+ /// @dev Classification only; does not require a nonzero embedded address.
52
55
  function isUser(bytes32 account) internal pure returns (bool) {
53
56
  return prefix(account) == User;
54
57
  }
55
58
 
56
- /// @notice Assert that `value` belongs to the EVM account family and return it unchanged.
59
+ /// @notice Assert that `value` belongs to the EVM account family, contains
60
+ /// a nonzero embedded address, and return it unchanged.
57
61
  /// @param value Account identifier to validate.
58
62
  /// @return account The same `value` if it is an EVM account.
59
63
  function evm(bytes32 value) internal pure returns (bytes32 account) {
60
- if (!isEvm(value)) revert InvalidAccount();
64
+ if (!isFamily(uint(value), Family)) revert InvalidAccount();
65
+ ensureAddr(address(uint160(uint(value) >> 32)));
61
66
  return value;
62
67
  }
63
68
 
@@ -69,19 +74,23 @@ library Accounts {
69
74
  return value;
70
75
  }
71
76
 
72
- /// @notice Assert that `value` is an admin account and return it unchanged.
77
+ /// @notice Assert that `value` is an admin account, contains a nonzero
78
+ /// embedded address, and return it unchanged.
73
79
  /// @param value Account identifier to validate.
74
80
  /// @return account The same `value` if it is an admin account.
75
81
  function admin(bytes32 value) internal pure returns (bytes32 account) {
76
- if (!isAdmin(value)) revert InvalidAccount();
82
+ if (uint32(uint(value) >> 224) != Admin) revert InvalidAccount();
83
+ ensureAddr(address(uint160(uint(value) >> 32)));
77
84
  return value;
78
85
  }
79
86
 
80
- /// @notice Assert that `value` is a user account and return it unchanged.
87
+ /// @notice Assert that `value` is a user account, contains a nonzero
88
+ /// embedded address, and return it unchanged.
81
89
  /// @param value Account identifier to validate.
82
90
  /// @return account The same `value` if it is a user account.
83
91
  function user(bytes32 value) internal pure returns (bytes32 account) {
84
- if (!isUser(value)) revert InvalidAccount();
92
+ if (uint32(uint(value) >> 224) != User) revert InvalidAccount();
93
+ ensureAddr(address(uint160(uint(value) >> 32)));
85
94
  return value;
86
95
  }
87
96
 
@@ -120,6 +129,6 @@ library Accounts {
120
129
  /// @param account EVM-family account ID.
121
130
  /// @return Embedded address (bits [191:32] of the ID).
122
131
  function addr(bytes32 account) internal pure returns (address) {
123
- return ensureAddr(address(uint160(uint(evm(account)) >> 32)));
132
+ return address(uint160(uint(evm(account)) >> 32));
124
133
  }
125
134
  }
package/utils/Errors.sol CHANGED
@@ -7,6 +7,12 @@ error InsufficientValue();
7
7
  /// @dev Thrown when a decoder or execution is finalized with unread data.
8
8
  error UnconsumedData();
9
9
 
10
+ /// @dev Thrown when an operation requires empty state but receives state data.
11
+ error UnexpectedState();
12
+
13
+ /// @dev Thrown when an operation requires empty input but receives input data.
14
+ error UnexpectedInput();
15
+
10
16
  /// @dev Thrown when an ID does not match the expected convention or type.
11
17
  error InvalidId();
12
18