@rootzero/contracts 0.9.3 → 0.9.4

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/commands/Base.sol CHANGED
@@ -2,11 +2,9 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import {NodeCalls} from "../core/Calls.sol";
5
- import {Cur} from "../Cursors.sol";
6
5
  import {CommandEvent} from "../events/Command.sol";
7
6
  import {Keys} from "../blocks/Keys.sol";
8
7
  import {Ids, Selectors} from "../utils/Ids.sol";
9
- import {Budget, Values} from "../utils/Value.sol";
10
8
 
11
9
  /// @notice Execution context passed to every command invocation.
12
10
  struct CommandContext {
@@ -25,22 +23,18 @@ struct CommandContext {
25
23
  abstract contract CommandBase is NodeCalls, CommandEvent {
26
24
  /// @dev Thrown when `onlyActive` finds that `deadline` has already passed.
27
25
  error Expired();
28
- /// @dev Thrown when the raw active account word in calldata does not match the decoded context account.
29
- error ActiveAccountMismatch();
30
26
  /// @dev Thrown when `onlyAdmin` finds that `account` is not the admin account.
31
27
  error NotAdmin();
32
28
 
33
- /// @dev Restrict execution to trusted callers whose decoded context account matches the active calldata account.
34
- modifier onlyCommand(bytes32 account) {
35
- if (activeAccount() != account) revert ActiveAccountMismatch();
36
- enforceCaller(msg.sender);
29
+ /// @dev Restrict execution to the commander using the host's admin account.
30
+ modifier onlyAdmin(bytes32 account) {
31
+ if (account != adminAccount) revert NotAdmin();
32
+ enforceCommander(msg.sender);
37
33
  _;
38
34
  }
39
35
 
40
- /// @dev Restrict execution to trusted callers using the host's admin account.
41
- modifier onlyAdmin(bytes32 account) {
42
- if (activeAccount() != account) revert ActiveAccountMismatch();
43
- if (account != adminAccount) revert NotAdmin();
36
+ /// @dev Restrict execution to trusted callers.
37
+ modifier onlyCommand() {
44
38
  enforceCaller(msg.sender);
45
39
  _;
46
40
  }
@@ -66,40 +60,4 @@ abstract contract CommandBase is NodeCalls, CommandEvent {
66
60
  function commandId(string memory name) internal view returns (uint) {
67
61
  return Ids.toCommand(Selectors.command(name), address(this));
68
62
  }
69
-
70
- /// @notice Return the active command account directly from the fixed tuple head in calldata.
71
- /// @dev Command entrypoints use the ABI shape `name((bytes32,bytes,bytes))`, so the tuple head
72
- /// starts at byte 36 of `msg.data`, with the account field at bytes [36:68).
73
- function activeAccount() internal pure returns (bytes32 account) {
74
- account = bytes32(msg.data[36:68]);
75
- }
76
- }
77
-
78
- /// @title CommandPayable
79
- /// @notice Abstract base for commands that accept native value (`msg.value`).
80
- /// Provides a shared settlement hook for any unspent value remaining in the
81
- /// command's mutable budget after execution completes.
82
- abstract contract CommandPayable is CommandBase {
83
- /// @dev Thrown when a payable command completes with unspent native value.
84
- /// Override `settleValue` to implement refund or forwarding behavior instead.
85
- error UnusedValue(uint remaining);
86
-
87
- /// @notice Drains the command budget and settles any remaining native value.
88
- /// @dev Calls the amount-based `settleValue` hook only when some value remains.
89
- /// @param account Caller's account identifier for the current invocation.
90
- /// @param budget Mutable native-value budget used during command execution.
91
- function settleValue(bytes32 account, Budget memory budget) internal {
92
- uint remaining = Values.drain(budget);
93
- if (remaining != 0) settleValue(account, remaining);
94
- }
95
-
96
- /// @notice Handles leftover native value after a payable command has finished.
97
- /// @dev Override this hook to refund or redirect unused value for a command.
98
- /// The default implementation rejects any leftover amount.
99
- /// @param account Caller's account identifier for the current invocation.
100
- /// @param remaining Unspent native value left in the budget, in wei.
101
- function settleValue(bytes32 account, uint remaining) internal virtual {
102
- account;
103
- revert UnusedValue(remaining);
104
- }
105
63
  }
package/commands/Burn.sol CHANGED
@@ -28,7 +28,7 @@ abstract contract Burn is CommandBase, BurnHook {
28
28
  emit Command(host, burnId, NAME, "0:1:0", "", Keys.Balance, Keys.Empty, false);
29
29
  }
30
30
 
31
- function burn(CommandContext calldata c) external onlyCommand(c.account) returns (bytes memory) {
31
+ function burn(CommandContext calldata c) external onlyCommand returns (bytes memory) {
32
32
  (Cur memory state, ) = cursor(c.state, 1);
33
33
 
34
34
  while (state.i < state.bound) {
@@ -36,7 +36,7 @@ abstract contract Burn is CommandBase, BurnHook {
36
36
  burn(c.account, asset, meta, amount);
37
37
  }
38
38
 
39
- state.complete();
39
+ state.close();
40
40
  return "";
41
41
  }
42
42
  }
@@ -22,7 +22,7 @@ abstract contract CreditAccountHook {
22
22
  /// An optional ACCOUNT block in the request overrides the default `c.account` destination.
23
23
  abstract contract CreditAccount is CommandBase, CreditAccountHook {
24
24
  string private constant NAME = "creditAccount";
25
- string private constant REQUEST = string.concat(Schemas.Empty, ";", Schemas.Account, "?");
25
+ string private constant REQUEST = string.concat(Schemas.Unit, ", maybe ", Schemas.Account);
26
26
 
27
27
  uint internal immutable creditAccountId = commandId(NAME);
28
28
 
@@ -32,7 +32,7 @@ abstract contract CreditAccount is CommandBase, CreditAccountHook {
32
32
 
33
33
  function creditAccount(
34
34
  CommandContext calldata c
35
- ) external onlyCommand(c.account) returns (bytes memory) {
35
+ ) external onlyCommand returns (bytes memory) {
36
36
  (Cur memory state, ) = cursor(c.state, 1);
37
37
  bytes32 to = Cursors.resolveAccount(c.request, c.account);
38
38
 
@@ -41,7 +41,7 @@ abstract contract CreditAccount is CommandBase, CreditAccountHook {
41
41
  creditAccount(to, asset, meta, amount);
42
42
  }
43
43
 
44
- state.complete();
44
+ state.close();
45
45
  return "";
46
46
  }
47
47
  }
@@ -43,12 +43,13 @@ abstract contract DebitAccount is CommandBase, DebitAccountHook {
43
43
  writer.appendBalance(asset, meta, amount);
44
44
  }
45
45
 
46
- return input.complete(writer);
46
+ input.close();
47
+ return writer.finish();
47
48
  }
48
49
 
49
50
  function debitAccount(
50
51
  CommandContext calldata c
51
- ) external onlyCommand(c.account) returns (bytes memory) {
52
+ ) external onlyCommand returns (bytes memory) {
52
53
  return debitAccount(c.account, c.request);
53
54
  }
54
55
  }
@@ -1,9 +1,10 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import { CommandContext, CommandBase, CommandPayable, Keys } from "./Base.sol";
4
+ import { CommandContext, CommandBase, Keys } from "./Base.sol";
5
+ import { Payable } from "../core/Payable.sol";
5
6
  import { Cursors, Cur, Schemas, Writer, Writers } from "../Cursors.sol";
6
- import { Budget, Values } from "../utils/Value.sol";
7
+ import { Budget } from "../utils/Value.sol";
7
8
 
8
9
  using Cursors for Cur;
9
10
  using Writers for Writer;
@@ -46,7 +47,7 @@ abstract contract Deposit is CommandBase, DepositHook {
46
47
 
47
48
  function deposit(
48
49
  CommandContext calldata c
49
- ) external onlyCommand(c.account) returns (bytes memory) {
50
+ ) external onlyCommand returns (bytes memory) {
50
51
  (Cur memory request, uint groups) = cursor(c.request, 1);
51
52
  Writer memory writer = Writers.allocBalances(groups);
52
53
 
@@ -56,14 +57,15 @@ abstract contract Deposit is CommandBase, DepositHook {
56
57
  writer.appendBalance(asset, meta, amount);
57
58
  }
58
59
 
59
- return request.complete(writer);
60
+ request.close();
61
+ return writer.finish();
60
62
  }
61
63
  }
62
64
 
63
65
  /// @title DepositPayable
64
66
  /// @notice Command that receives externally sourced assets and records them as BALANCE state.
65
67
  /// Use `depositPayable` when the hook needs tracked access to `msg.value` via a mutable budget.
66
- abstract contract DepositPayable is CommandPayable, DepositPayableHook {
68
+ abstract contract DepositPayable is CommandBase, Payable, DepositPayableHook {
67
69
  string private constant NAME = "depositPayable";
68
70
 
69
71
  uint internal immutable depositPayableId = commandId(NAME);
@@ -74,10 +76,10 @@ abstract contract DepositPayable is CommandPayable, DepositPayableHook {
74
76
 
75
77
  function depositPayable(
76
78
  CommandContext calldata c
77
- ) external payable onlyCommand(c.account) returns (bytes memory) {
79
+ ) external payable onlyCommand returns (bytes memory) {
78
80
  (Cur memory request, uint groups) = cursor(c.request, 1);
79
81
  Writer memory writer = Writers.allocBalances(groups);
80
- Budget memory budget = Values.fromMsg();
82
+ Budget memory budget = valueBudget();
81
83
 
82
84
  while (request.i < request.bound) {
83
85
  (bytes32 asset, bytes32 meta, uint amount) = request.unpackAmount();
@@ -86,7 +88,8 @@ abstract contract DepositPayable is CommandPayable, DepositPayableHook {
86
88
  }
87
89
 
88
90
  settleValue(c.account, budget);
89
- return request.complete(writer);
91
+ request.close();
92
+ return writer.finish();
90
93
  }
91
94
  }
92
95
 
@@ -1,9 +1,10 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import {CommandContext, CommandBase, CommandPayable, Keys} from "./Base.sol";
4
+ import {CommandContext, CommandBase, Keys} from "./Base.sol";
5
+ import {Payable} from "../core/Payable.sol";
5
6
  import {HostAmount, Cursors, Cur, Schemas, Writer, Writers} from "../Cursors.sol";
6
- import {Budget, Values} from "../utils/Value.sol";
7
+ import {Budget} from "../utils/Value.sol";
7
8
  using Cursors for Cur;
8
9
  using Writers for Writer;
9
10
 
@@ -40,7 +41,7 @@ abstract contract Provision is CommandBase, ProvisionHook {
40
41
  emit Command(host, provisionId, NAME, "1:0:1", Schemas.Allocation, Keys.Empty, Keys.Custody, false);
41
42
  }
42
43
 
43
- function provision(CommandContext calldata c) external onlyCommand(c.account) returns (bytes memory) {
44
+ function provision(CommandContext calldata c) external onlyCommand returns (bytes memory) {
44
45
  (Cur memory request, uint groups) = cursor(c.request, 1);
45
46
  Writer memory writer = Writers.allocCustodies(groups);
46
47
 
@@ -50,7 +51,8 @@ abstract contract Provision is CommandBase, ProvisionHook {
50
51
  writer.appendCustody(allocation);
51
52
  }
52
53
 
53
- return request.complete(writer);
54
+ request.close();
55
+ return writer.finish();
54
56
  }
55
57
  }
56
58
 
@@ -58,7 +60,7 @@ abstract contract Provision is CommandBase, ProvisionHook {
58
60
  /// @notice Command that provisions assets to peer hosts from ALLOCATION request blocks.
59
61
  /// Each request block supplies the target host plus an asset amount; the output is a CUSTODY state stream.
60
62
  /// The hook receives a mutable native-value budget drawn from `msg.value`.
61
- abstract contract ProvisionPayable is CommandPayable, ProvisionPayableHook {
63
+ abstract contract ProvisionPayable is CommandBase, Payable, ProvisionPayableHook {
62
64
  string private constant NAME = "provisionPayable";
63
65
 
64
66
  uint internal immutable provisionPayableId = commandId(NAME);
@@ -69,10 +71,10 @@ abstract contract ProvisionPayable is CommandPayable, ProvisionPayableHook {
69
71
 
70
72
  function provisionPayable(
71
73
  CommandContext calldata c
72
- ) external payable onlyCommand(c.account) returns (bytes memory) {
74
+ ) external payable onlyCommand returns (bytes memory) {
73
75
  (Cur memory request, uint groups) = cursor(c.request, 1);
74
76
  Writer memory writer = Writers.allocCustodies(groups);
75
- Budget memory budget = Values.fromMsg();
77
+ Budget memory budget = valueBudget();
76
78
 
77
79
  while (request.i < request.bound) {
78
80
  HostAmount memory allocation = request.unpackAllocationValue();
@@ -81,7 +83,8 @@ abstract contract ProvisionPayable is CommandPayable, ProvisionPayableHook {
81
83
  }
82
84
 
83
85
  settleValue(c.account, budget);
84
- return request.complete(writer);
86
+ request.close();
87
+ return writer.finish();
85
88
  }
86
89
  }
87
90
 
@@ -42,13 +42,13 @@ abstract contract Transfer is CommandBase, TransferHook {
42
42
  transfer(value);
43
43
  }
44
44
 
45
- input.complete();
45
+ input.close();
46
46
  return "";
47
47
  }
48
48
 
49
49
  function transfer(
50
50
  CommandContext calldata c
51
- ) external onlyCommand(c.account) returns (bytes memory) {
51
+ ) external onlyCommand returns (bytes memory) {
52
52
  return transfer(c.account, c.request);
53
53
  }
54
54
  }
@@ -21,7 +21,7 @@ abstract contract WithdrawHook {
21
21
  /// For internal balance credits, use `creditAccount` instead.
22
22
  abstract contract Withdraw is CommandBase, WithdrawHook {
23
23
  string private constant NAME = "withdraw";
24
- string private constant REQUEST = string.concat(Schemas.Empty, ";", Schemas.Account, "?");
24
+ string private constant REQUEST = string.concat(Schemas.Unit, ", maybe ", Schemas.Account);
25
25
 
26
26
  uint internal immutable withdrawId = commandId(NAME);
27
27
 
@@ -31,7 +31,7 @@ abstract contract Withdraw is CommandBase, WithdrawHook {
31
31
 
32
32
  function withdraw(
33
33
  CommandContext calldata c
34
- ) external onlyCommand(c.account) returns (bytes memory) {
34
+ ) external onlyCommand returns (bytes memory) {
35
35
  (Cur memory state, ) = cursor(c.state, 1);
36
36
  bytes32 to = Cursors.resolveAccount(c.request, c.account);
37
37
 
@@ -40,7 +40,7 @@ abstract contract Withdraw is CommandBase, WithdrawHook {
40
40
  withdraw(to, asset, meta, amount);
41
41
  }
42
42
 
43
- state.complete();
43
+ state.close();
44
44
  return "";
45
45
  }
46
46
  }
@@ -34,7 +34,7 @@ abstract contract AllowAssets is CommandBase, AdminEvent, AllowAssetsHook {
34
34
  allowAsset(asset, meta);
35
35
  }
36
36
 
37
- request.complete();
37
+ request.close();
38
38
  return "";
39
39
  }
40
40
  }
@@ -37,7 +37,7 @@ abstract contract Allowance is CommandBase, AdminEvent, AllowanceHook {
37
37
  allowance(peer, asset, meta, amount);
38
38
  }
39
39
 
40
- request.complete();
40
+ request.close();
41
41
  return "";
42
42
  }
43
43
  }
@@ -29,7 +29,7 @@ abstract contract Authorize is CommandBase, AdminEvent {
29
29
  authorize(node);
30
30
  }
31
31
 
32
- request.complete();
32
+ request.close();
33
33
  return "";
34
34
  }
35
35
  }
@@ -34,7 +34,7 @@ abstract contract DenyAssets is CommandBase, AdminEvent, DenyAssetsHook {
34
34
  denyAsset(asset, meta);
35
35
  }
36
36
 
37
- request.complete();
37
+ request.close();
38
38
  return "";
39
39
  }
40
40
  }
@@ -1,10 +1,11 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import {CommandContext, CommandPayable, Keys} from "../Base.sol";
4
+ import {CommandBase, CommandContext, Keys} from "../Base.sol";
5
+ import {Payable} from "../../core/Payable.sol";
5
6
  import {Cursors, Cur, Schemas} from "../../Cursors.sol";
6
7
  import {AdminEvent} from "../../events/Admin.sol";
7
- import {Budget, Values} from "../../utils/Value.sol";
8
+ import {Budget} from "../../utils/Value.sol";
8
9
  import {Ids} from "../../utils/Ids.sol";
9
10
 
10
11
  using Cursors for Cur;
@@ -13,7 +14,7 @@ using Cursors for Cur;
13
14
  /// @notice Admin command that forwards raw calldata to one or more target nodes.
14
15
  /// Each CALL block specifies a target node ID, native value, and raw calldata payload.
15
16
  /// Only callable by the admin account.
16
- abstract contract ExecutePayable is CommandPayable, AdminEvent {
17
+ abstract contract ExecutePayable is CommandBase, Payable, AdminEvent {
17
18
  string private constant NAME = "executePayable";
18
19
 
19
20
  uint internal immutable executePayableId = commandId(NAME);
@@ -24,15 +25,15 @@ abstract contract ExecutePayable is CommandPayable, AdminEvent {
24
25
 
25
26
  function executePayable(CommandContext calldata c) external payable onlyAdmin(c.account) returns (bytes memory) {
26
27
  (Cur memory request, ) = cursor(c.request, 1);
27
- Budget memory budget = Values.fromMsg();
28
+ Budget memory budget = valueBudget();
28
29
 
29
30
  while (request.i < request.bound) {
30
31
  (uint target, uint value, bytes calldata data) = request.unpackCall();
31
32
  address addr = Ids.nodeAddr(target);
32
- callAddr(addr, Values.use(budget, value), data);
33
+ callAddr(addr, useValue(budget, value), data);
33
34
  }
34
35
 
35
- request.complete();
36
+ request.close();
36
37
  settleValue(c.account, budget);
37
38
  return "";
38
39
  }
@@ -29,7 +29,7 @@ abstract contract Unauthorize is CommandBase, AdminEvent {
29
29
  unauthorize(node);
30
30
  }
31
31
 
32
- request.complete();
32
+ request.close();
33
33
  return "";
34
34
  }
35
35
  }
package/core/Access.sol CHANGED
@@ -78,4 +78,15 @@ abstract contract AccessControl is RootZeroContext, AccessEvent {
78
78
  }
79
79
  return caller;
80
80
  }
81
+
82
+ /// @notice Assert that `caller` is the commander and return it.
83
+ /// Used by admin modifiers to keep governance authority separate from peer trust.
84
+ /// @param caller Address to validate.
85
+ /// @return The same `caller` value if it is the commander.
86
+ function enforceCommander(address caller) internal view returns (address) {
87
+ if (caller == address(0) || caller != commander) {
88
+ revert UnauthorizedCaller(caller);
89
+ }
90
+ return caller;
91
+ }
81
92
  }
package/core/Context.sol CHANGED
@@ -29,8 +29,7 @@ abstract contract RootZeroContext {
29
29
  /// @return cur Cursor with `bound` set to the end of the first run.
30
30
  /// @return groups Number of block groups in the run (`prime block count / group`).
31
31
  function cursor(bytes calldata source, uint group) internal pure returns (Cur memory cur, uint groups) {
32
- cur = Cursors.open(source);
33
- (, , groups) = cur.primeRun(group);
32
+ return Cursors.init(source, group);
34
33
  }
35
34
 
36
35
  /// @notice Open a cursor, prime it, and assert that its group count matches `expectedGroups`.
@@ -41,8 +40,8 @@ abstract contract RootZeroContext {
41
40
  /// @param expectedGroups Required number of groups in the first run.
42
41
  /// @return cur Cursor with `bound` set to the end of the first run.
43
42
  function cursor(bytes calldata source, uint group, uint expectedGroups) internal pure returns (Cur memory cur) {
44
- cur = Cursors.open(source);
45
- (, , uint groups) = cur.primeRun(group);
43
+ uint groups;
44
+ (cur, groups) = Cursors.init(source, group);
46
45
  if (groups != expectedGroups) revert Cursors.BadRatio();
47
46
  }
48
47
  }
@@ -0,0 +1,57 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import {Budget, Values} from "../utils/Value.sol";
5
+
6
+ /// @title Payable
7
+ /// @notice Abstract mixin for entrypoints that accept native value (`msg.value`).
8
+ /// Provides a shared settlement hook for any unspent value remaining in the
9
+ /// mutable budget after execution completes.
10
+ abstract contract Payable {
11
+ /// @dev Thrown when a payable entrypoint completes with unspent native value.
12
+ /// Override `settleValue` to implement refund or forwarding behavior instead.
13
+ error UnusedValue(uint remaining);
14
+
15
+ /// @notice Create a native-value budget from the current call's `msg.value`.
16
+ /// @return Budget initialised with the full `msg.value`.
17
+ function valueBudget() internal view returns (Budget memory) {
18
+ return Budget({remaining: msg.value});
19
+ }
20
+
21
+ /// @notice Deduct `amount` from the budget and return it.
22
+ /// @param budget Mutable budget to deduct from.
23
+ /// @param amount Native value to spend.
24
+ /// @return The same `amount`, ready to forward to a callee.
25
+ function useValue(Budget memory budget, uint amount) internal pure returns (uint) {
26
+ return Values.use(budget, amount);
27
+ }
28
+
29
+ /// @notice Deduct `amount` from the budget and return it as a new sub-budget.
30
+ /// @param budget Mutable parent budget to deduct from.
31
+ /// @param amount Native value to assign to the sub-budget.
32
+ /// @return A new budget with `amount` remaining.
33
+ function allocateValue(Budget memory budget, uint amount) internal pure returns (Budget memory) {
34
+ return Values.allocate(budget, amount);
35
+ }
36
+
37
+ /// @notice Drains the budget and settles any remaining native value.
38
+ /// @dev Calls the amount-based `settleValue` hook only when some value remains.
39
+ /// @param account Account identifier for the current invocation.
40
+ /// @param budget Mutable native-value budget used during execution.
41
+ function settleValue(bytes32 account, Budget memory budget) internal {
42
+ uint value = budget.remaining;
43
+ if (value == 0) return;
44
+ budget.remaining = 0;
45
+ settleValue(account, value);
46
+ }
47
+
48
+ /// @notice Handles leftover native value after payable execution has finished.
49
+ /// @dev Override this hook to refund or redirect unused value.
50
+ /// The default implementation rejects any leftover amount.
51
+ /// @param account Account identifier for the current invocation.
52
+ /// @param remaining Unspent native value left in the budget, in wei.
53
+ function settleValue(bytes32 account, uint remaining) internal virtual {
54
+ account;
55
+ revert UnusedValue(remaining);
56
+ }
57
+ }
@@ -0,0 +1,55 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import {Cursors, Cur} from "../Cursors.sol";
5
+ import {Payable} from "./Payable.sol";
6
+ import {Budget} from "../utils/Value.sol";
7
+
8
+ using Cursors for Cur;
9
+
10
+ /// @title Pipeline
11
+ /// @notice Core pipeline functionality shared by higher-level surfaces.
12
+ abstract contract Pipeline is Payable {
13
+ error UnexpectedState();
14
+
15
+ /// @notice Override to dispatch one piped step.
16
+ /// Called once per STEP block. The returned bytes become the state passed to
17
+ /// the next step, and the final returned state must be empty.
18
+ /// @param target Node ID to invoke or handle.
19
+ /// @param account Account identifier for the piped context.
20
+ /// @param state Current threaded state block stream.
21
+ /// @param request Step request block stream.
22
+ /// @param value Native value assigned to this step.
23
+ /// @return Updated state block stream for the next step.
24
+ function dispatch(
25
+ uint target,
26
+ bytes32 account,
27
+ bytes memory state,
28
+ bytes calldata request,
29
+ uint value
30
+ ) internal virtual returns (bytes memory);
31
+
32
+ /// @notice Execute a STEP block stream through the pipeline.
33
+ /// @dev Reverts with `UnexpectedState` if the final threaded state is non-empty.
34
+ /// @param account Account identifier used for each dispatched step.
35
+ /// @param state Initial state block stream passed to the first step.
36
+ /// @param steps STEP block stream to execute.
37
+ /// @param budget Mutable native-value budget shared across all steps.
38
+ function pipe(
39
+ bytes32 account,
40
+ bytes memory state,
41
+ bytes calldata steps,
42
+ Budget memory budget
43
+ ) internal {
44
+ (Cur memory input, ) = Cursors.init(steps, 1);
45
+
46
+ while (input.i < input.bound) {
47
+ (uint target, uint value, bytes calldata request) = input.unpackStep();
48
+ state = dispatch(target, account, state, request, useValue(budget, value));
49
+ }
50
+
51
+ if (state.length != 0) revert UnexpectedState();
52
+ settleValue(account, budget);
53
+ input.close();
54
+ }
55
+ }