@rootzero/contracts 0.9.3 → 0.9.5

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 (49) hide show
  1. package/Commands.sol +3 -2
  2. package/Core.sol +4 -3
  3. package/Cursors.sol +1 -1
  4. package/Events.sol +2 -3
  5. package/README.md +18 -24
  6. package/blocks/Cursors.sol +316 -337
  7. package/blocks/Keys.sol +40 -57
  8. package/blocks/Schema.sol +57 -208
  9. package/blocks/Writers.sol +376 -135
  10. package/commands/Base.sol +6 -48
  11. package/commands/Burn.sol +2 -2
  12. package/commands/Credit.sol +3 -3
  13. package/commands/Debit.sol +3 -2
  14. package/commands/Deposit.sol +11 -8
  15. package/commands/Provision.sol +11 -8
  16. package/commands/Transfer.sol +2 -2
  17. package/commands/Withdraw.sol +3 -3
  18. package/commands/admin/AllowAssets.sol +1 -1
  19. package/commands/admin/Allowance.sol +1 -1
  20. package/commands/admin/Authorize.sol +1 -1
  21. package/commands/admin/DenyAssets.sol +1 -1
  22. package/commands/admin/Execute.sol +7 -6
  23. package/commands/admin/Unauthorize.sol +1 -1
  24. package/core/Access.sol +11 -0
  25. package/core/Calls.sol +5 -5
  26. package/core/Context.sol +3 -4
  27. package/core/Host.sol +25 -20
  28. package/core/Payable.sol +57 -0
  29. package/core/Pipeline.sol +55 -0
  30. package/docs/Schema.md +196 -0
  31. package/events/Command.sol +1 -2
  32. package/events/Introduction.sol +22 -0
  33. package/events/{Piped.sol → Rooted.sol} +3 -3
  34. package/package.json +2 -2
  35. package/peer/AllowAssets.sol +1 -1
  36. package/peer/Allowance.sol +1 -1
  37. package/peer/BalancePull.sol +1 -1
  38. package/peer/DenyAssets.sol +1 -1
  39. package/peer/Pipe.sol +38 -0
  40. package/peer/Settle.sol +1 -1
  41. package/queries/Assets.sol +4 -3
  42. package/queries/Balances.sol +2 -1
  43. package/queries/Positions.sol +12 -12
  44. package/utils/Value.sol +8 -14
  45. package/commands/Pipe.sol +0 -67
  46. package/docs/GETTING_STARTED.md +0 -294
  47. package/events/Governed.sol +0 -21
  48. package/events/Host.sol +0 -22
  49. package/interfaces/IHostDiscovery.sol +0 -16
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/Calls.sol CHANGED
@@ -72,13 +72,13 @@ abstract contract NodeCalls is AccessControl {
72
72
  }
73
73
 
74
74
  /// @notice Encode and call a trusted command node.
75
- /// @param ctx Command execution context.
76
- /// @param cid Command node ID embedding the target selector.
75
+ /// @param id Command node ID embedding the target selector.
77
76
  /// @param value Native value to forward in wei.
77
+ /// @param ctx Command execution context.
78
78
  /// @return Decoded command output block stream.
79
- function callCommand(CommandContext memory ctx, uint cid, uint value) internal returns (bytes memory) {
80
- bytes4 selector = Ids.commandSelector(cid);
79
+ function callCommand(uint id, uint value, CommandContext memory ctx) internal returns (bytes memory) {
80
+ bytes4 selector = Ids.commandSelector(id);
81
81
  bytes memory data = abi.encodeWithSelector(selector, ctx);
82
- return abi.decode(callTo(cid, value, data), (bytes));
82
+ return abi.decode(callTo(id, value, data), (bytes));
83
83
  }
84
84
  }
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
  }
package/core/Host.sol CHANGED
@@ -5,38 +5,43 @@ import {AccessControl} from "./Access.sol";
5
5
  import {Authorize} from "../commands/admin/Authorize.sol";
6
6
  import {Unauthorize} from "../commands/admin/Unauthorize.sol";
7
7
  import {ExecutePayable} from "../commands/admin/Execute.sol";
8
- import {HostAnnouncedEvent} from "../events/Host.sol";
9
- import {IHostDiscovery} from "../interfaces/IHostDiscovery.sol";
10
- import {Ids} from "../utils/Ids.sol";
8
+ import {IntroductionEvent} from "../events/Introduction.sol";
11
9
 
12
- /// @notice Mixin that allows a contract to act as a host discovery registry.
13
- /// Hosts call `announceHost` on a discovery contract to register themselves.
14
- abstract contract HostDiscovery is HostAnnouncedEvent, IHostDiscovery {
15
- /// @notice Announce this host to the discovery registry.
16
- /// Validates that `id` matches `msg.sender` before emitting.
17
- /// @param id Host node ID (must equal `Ids.toHost(msg.sender)`).
18
- /// @param blocknum Block number at which the host was deployed.
19
- /// @param version Protocol version the host implements.
20
- /// @param namespace Human-readable namespace string for the host.
21
- function announceHost(uint id, uint blocknum, uint16 version, string calldata namespace) external {
22
- emit HostAnnounced(Ids.matchHost(id, msg.sender), blocknum, version, namespace);
23
- }
10
+ /// @title IHostIntroduction
11
+ /// @notice Interface implemented by hosts that accept introductions from other hosts.
12
+ interface IHostIntroduction {
13
+ /// @notice Record a host introduction claim.
14
+ /// @param peer Host node ID being introduced.
15
+ /// @param blocknum Block number at which the introduction was made.
16
+ /// @param version Protocol version the host is running.
17
+ /// @param namespace Human-readable namespace or label for the host.
18
+ function introduce(uint peer, uint blocknum, uint16 version, string calldata namespace) external;
24
19
  }
25
20
 
26
21
  /// @title Host
27
22
  /// @notice Abstract base contract for rootzero host implementations.
28
23
  /// Inherits admin command support (authorize, unauthorize, executePayable) and
29
- /// optionally announces itself to a discovery contract at deployment.
24
+ /// optionally introduces itself to a commander host at deployment.
30
25
  /// Accepts native ETH payments via the `receive` function.
31
- abstract contract Host is Authorize, Unauthorize, ExecutePayable {
26
+ abstract contract Host is Authorize, Unauthorize, ExecutePayable, IntroductionEvent, IHostIntroduction {
32
27
  /// @param cmdr Commander address; passed to `AccessControl`.
33
- /// If `cmdr` is a deployed contract, the host calls `announceHost`
34
- /// on it during construction to register with the discovery registry.
28
+ /// If `cmdr` is a deployed contract, the host calls `introduce`
29
+ /// on it during construction.
35
30
  /// @param version Protocol version number to publish in the announcement.
36
31
  /// @param namespace Human-readable namespace string for the host.
37
32
  constructor(address cmdr, uint16 version, string memory namespace) AccessControl(cmdr) {
38
33
  if (cmdr == address(0) || cmdr == address(this) || cmdr.code.length == 0) return;
39
- IHostDiscovery(cmdr).announceHost(host, block.number, version, namespace);
34
+ IHostIntroduction(cmdr).introduce(host, block.number, version, namespace);
35
+ }
36
+
37
+ /// @notice Record a host introduction claim.
38
+ /// @dev This event is informational only; it does not authorize or trust the introduced host.
39
+ /// @param peer Host node ID being introduced.
40
+ /// @param blocknum Block number at which the host was deployed.
41
+ /// @param version Protocol version the host implements.
42
+ /// @param namespace Human-readable namespace string for the host.
43
+ function introduce(uint peer, uint blocknum, uint16 version, string calldata namespace) external {
44
+ emit Introduction(peer, blocknum, version, namespace);
40
45
  }
41
46
 
42
47
  /// @notice Accept native ETH transfers (e.g. from command value flows).
@@ -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
+ }