@rootzero/contracts 0.7.2 → 0.9.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 (71) hide show
  1. package/Commands.sol +15 -20
  2. package/Core.sol +3 -4
  3. package/Cursors.sol +3 -2
  4. package/Events.sol +1 -1
  5. package/Queries.sol +3 -3
  6. package/README.md +18 -19
  7. package/Utils.sol +3 -3
  8. package/blocks/Cursors.sol +937 -551
  9. package/blocks/Keys.sol +60 -34
  10. package/blocks/Schema.sol +112 -122
  11. package/blocks/Writers.sol +476 -301
  12. package/commands/Base.sol +32 -22
  13. package/commands/Burn.sol +14 -12
  14. package/commands/Credit.sol +16 -15
  15. package/commands/Debit.sol +14 -12
  16. package/commands/Deposit.sol +30 -37
  17. package/commands/Pipe.sol +14 -20
  18. package/commands/Provision.sol +19 -49
  19. package/commands/Transfer.sol +9 -18
  20. package/commands/Withdraw.sol +15 -14
  21. package/commands/admin/AllowAssets.sol +3 -3
  22. package/commands/admin/Allowance.sol +43 -0
  23. package/commands/admin/Authorize.sol +4 -4
  24. package/commands/admin/DenyAssets.sol +3 -3
  25. package/commands/admin/Destroy.sol +10 -8
  26. package/commands/admin/Execute.sol +38 -0
  27. package/commands/admin/Init.sol +10 -8
  28. package/commands/admin/Relocate.sol +5 -5
  29. package/commands/admin/Unauthorize.sol +4 -4
  30. package/core/Access.sol +38 -34
  31. package/core/Balances.sol +17 -18
  32. package/core/{Operation.sol → Calls.sol} +5 -8
  33. package/core/{CursorBase.sol → Context.sol} +11 -5
  34. package/core/Host.sol +11 -10
  35. package/core/Types.sol +86 -0
  36. package/docs/GETTING_STARTED.md +37 -29
  37. package/events/Asset.sol +1 -1
  38. package/events/Command.sol +10 -10
  39. package/events/Deposit.sol +3 -4
  40. package/events/Listing.sol +1 -1
  41. package/events/Peer.sol +3 -3
  42. package/events/Position.sol +21 -0
  43. package/events/Query.sol +3 -3
  44. package/events/Withdraw.sol +2 -3
  45. package/package.json +1 -1
  46. package/peer/AllowAssets.sol +1 -1
  47. package/peer/Allowance.sol +36 -0
  48. package/peer/AssetPull.sol +33 -31
  49. package/peer/Base.sol +8 -4
  50. package/peer/DenyAssets.sol +1 -1
  51. package/peer/Settle.sol +3 -4
  52. package/queries/Assets.sol +18 -16
  53. package/queries/Balances.sol +21 -19
  54. package/queries/Base.sol +2 -3
  55. package/queries/Positions.sol +32 -24
  56. package/utils/Accounts.sol +14 -13
  57. package/utils/Assets.sol +137 -62
  58. package/utils/Ids.sol +9 -9
  59. package/utils/Layout.sol +5 -3
  60. package/utils/Utils.sol +10 -0
  61. package/commands/Create.sol +0 -42
  62. package/commands/Remove.sol +0 -42
  63. package/commands/Settle.sol +0 -38
  64. package/commands/Stake.sol +0 -47
  65. package/commands/Supply.sol +0 -41
  66. package/commands/admin/Allocate.sol +0 -41
  67. package/core/HostBound.sol +0 -14
  68. package/events/Erc721.sol +0 -20
  69. package/peer/Pull.sol +0 -39
  70. package/peer/Push.sol +0 -45
  71. package/utils/State.sol +0 -22
@@ -1,37 +1,38 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import { CommandContext, CommandBase, State } from "./Base.sol";
4
+ import { CommandContext, CommandBase, Keys } from "./Base.sol";
5
5
  import { Cursors, Cur, Schemas } from "../Cursors.sol";
6
6
  using Cursors for Cur;
7
7
 
8
8
  string constant NAME = "withdraw";
9
9
 
10
+ abstract contract WithdrawHook {
11
+ /// @notice Override to send funds to `account`.
12
+ /// Called once per BALANCE block in state.
13
+ /// @param account Destination account identifier (resolved from ACCOUNT block or caller).
14
+ /// @param asset Asset identifier.
15
+ /// @param meta Asset metadata slot.
16
+ /// @param amount Amount to deliver.
17
+ function withdraw(bytes32 account, bytes32 asset, bytes32 meta, uint amount) internal virtual;
18
+ }
19
+
10
20
  /// @title Withdraw
11
21
  /// @notice Command that delivers BALANCE state blocks to an external destination.
12
22
  /// Use `withdraw` for assets being sent outside the protocol (e.g. ERC-20 transfers, ETH sends).
13
23
  /// For internal balance credits, use `creditAccount` instead.
14
- abstract contract Withdraw is CommandBase {
24
+ abstract contract Withdraw is CommandBase, WithdrawHook {
15
25
  uint internal immutable withdrawId = commandId(NAME);
16
26
 
17
27
  constructor() {
18
- emit Command(host, NAME, Schemas.Recipient, withdrawId, State.Balances, State.Empty, false);
28
+ emit Command(host, withdrawId, NAME, Schemas.Account, Keys.Balance, Keys.Empty, false);
19
29
  }
20
30
 
21
- /// @notice Override to send funds to `account`.
22
- /// Called once per BALANCE block in state.
23
- /// @param account Destination account identifier (resolved from RECIPIENT block or caller).
24
- /// @param asset Asset identifier.
25
- /// @param meta Asset metadata slot.
26
- /// @param amount Amount to deliver.
27
- function withdraw(bytes32 account, bytes32 asset, bytes32 meta, uint amount) internal virtual;
28
-
29
31
  function withdraw(
30
32
  CommandContext calldata c
31
- ) external onlyCommand(withdrawId, c.target) returns (bytes memory) {
33
+ ) external onlyCommand(c.account) returns (bytes memory) {
32
34
  (Cur memory state, , ) = cursor(c.state, 1);
33
- Cur memory request = cursor(c.request);
34
- bytes32 to = request.recipientAfter(c.account);
35
+ bytes32 to = Cursors.resolveAccount(c.request, c.account);
35
36
 
36
37
  while (state.i < state.bound) {
37
38
  (bytes32 asset, bytes32 meta, uint amount) = state.unpackBalance();
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import { CommandBase, CommandContext, State } from "../Base.sol";
4
+ import { CommandBase, CommandContext, Keys } from "../Base.sol";
5
5
  import { Cursors, Cur, Schemas } from "../../Cursors.sol";
6
6
  using Cursors for Cur;
7
7
 
@@ -20,12 +20,12 @@ abstract contract AllowAssets is CommandBase, AllowAssetsHook {
20
20
  uint internal immutable allowAssetsId = commandId(NAME);
21
21
 
22
22
  constructor() {
23
- emit Command(host, NAME, Schemas.Asset, allowAssetsId, State.Empty, State.Empty, false);
23
+ emit Command(host, allowAssetsId, NAME, Schemas.Asset, Keys.Empty, Keys.Empty, false);
24
24
  }
25
25
 
26
26
  function allowAssets(
27
27
  CommandContext calldata c
28
- ) external onlyAdmin(c.account) onlyCommand(allowAssetsId, c.target) returns (bytes memory) {
28
+ ) external onlyAdmin(c.account) returns (bytes memory) {
29
29
  (Cur memory request, , ) = cursor(c.request, 1);
30
30
 
31
31
  while (request.i < request.bound) {
@@ -0,0 +1,43 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import { CommandBase, CommandContext, Keys } from "../Base.sol";
5
+ import { Cursors, Cur, Schemas } from "../../Cursors.sol";
6
+ using Cursors for Cur;
7
+
8
+ string constant NAME = "allowance";
9
+
10
+ abstract contract AllowanceHook {
11
+ /// @notice Apply or revoke one host-scoped allowance.
12
+ /// Called once per ALLOWANCE block in the request. Implementations decide
13
+ /// how the allowance is represented, e.g. ERC-20 approval, an internal cap,
14
+ /// or another host-specific authorization record.
15
+ /// @param peer Host node receiving the allowed cap.
16
+ /// @param asset Asset identifier.
17
+ /// @param meta Asset metadata slot.
18
+ /// @param amount Allowed cap amount.
19
+ function allowance(uint peer, bytes32 asset, bytes32 meta, uint amount) internal virtual;
20
+ }
21
+
22
+ /// @title Allowance
23
+ /// @notice Admin command that applies cross-host allowance entries via a virtual hook.
24
+ /// Each ALLOWANCE block grants or updates a host-scoped asset cap. Only callable by the admin account.
25
+ abstract contract Allowance is CommandBase, AllowanceHook {
26
+ uint internal immutable allowanceId = commandId(NAME);
27
+
28
+ constructor() {
29
+ emit Command(host, allowanceId, NAME, Schemas.Allowance, Keys.Empty, Keys.Empty, false);
30
+ }
31
+
32
+ function allowance(CommandContext calldata c) external onlyAdmin(c.account) returns (bytes memory) {
33
+ (Cur memory request, , ) = cursor(c.request, 1);
34
+
35
+ while (request.i < request.bound) {
36
+ (uint peer, bytes32 asset, bytes32 meta, uint amount) = request.unpackAllowance();
37
+ allowance(peer, asset, meta, amount);
38
+ }
39
+
40
+ request.complete();
41
+ return "";
42
+ }
43
+ }
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import { CommandBase, CommandContext, State } from "../Base.sol";
4
+ import { CommandBase, CommandContext, Keys } from "../Base.sol";
5
5
  import { Cursors, Cur, Schemas } from "../../Cursors.sol";
6
6
  using Cursors for Cur;
7
7
 
@@ -15,17 +15,17 @@ abstract contract Authorize is CommandBase {
15
15
  uint internal immutable authorizeId = commandId(NAME);
16
16
 
17
17
  constructor() {
18
- emit Command(host, NAME, Schemas.Node, authorizeId, State.Empty, State.Empty, false);
18
+ emit Command(host, authorizeId, NAME, Schemas.Node, Keys.Empty, Keys.Empty, false);
19
19
  }
20
20
 
21
21
  function authorize(
22
22
  CommandContext calldata c
23
- ) external onlyAdmin(c.account) onlyCommand(authorizeId, c.target) returns (bytes memory) {
23
+ ) external onlyAdmin(c.account) returns (bytes memory) {
24
24
  (Cur memory request, , ) = cursor(c.request, 1);
25
25
 
26
26
  while (request.i < request.bound) {
27
27
  uint node = request.unpackNode();
28
- access(node, true);
28
+ authorize(node);
29
29
  }
30
30
 
31
31
  request.complete();
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import { CommandBase, CommandContext, State } from "../Base.sol";
4
+ import { CommandBase, CommandContext, Keys } from "../Base.sol";
5
5
  import { Cursors, Cur, Schemas } from "../../Cursors.sol";
6
6
  using Cursors for Cur;
7
7
 
@@ -20,12 +20,12 @@ abstract contract DenyAssets is CommandBase, DenyAssetsHook {
20
20
  uint internal immutable denyAssetsId = commandId(NAME);
21
21
 
22
22
  constructor() {
23
- emit Command(host, NAME, Schemas.Asset, denyAssetsId, State.Empty, State.Empty, false);
23
+ emit Command(host, denyAssetsId, NAME, Schemas.Asset, Keys.Empty, Keys.Empty, false);
24
24
  }
25
25
 
26
26
  function denyAssets(
27
27
  CommandContext calldata c
28
- ) external onlyAdmin(c.account) onlyCommand(denyAssetsId, c.target) returns (bytes memory) {
28
+ ) external onlyAdmin(c.account) returns (bytes memory) {
29
29
  (Cur memory request, , ) = cursor(c.request, 1);
30
30
 
31
31
  while (request.i < request.bound) {
@@ -1,30 +1,32 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import { CommandBase, CommandContext, State } from "../Base.sol";
4
+ import { CommandBase, CommandContext, Keys } from "../Base.sol";
5
5
  import { Cursors, Cur } from "../../Cursors.sol";
6
6
 
7
7
  string constant NAME = "destroy";
8
8
 
9
9
  using Cursors for Cur;
10
10
 
11
+ abstract contract DestroyHook {
12
+ /// @notice Override to run host teardown or destruction logic.
13
+ /// @param input Cursor over the full request byte stream.
14
+ function destroy(Cur memory input) internal virtual;
15
+ }
16
+
11
17
  /// @title Destroy
12
18
  /// @notice Admin command that runs host teardown logic via a virtual hook.
13
19
  /// The full request is passed to `destroy` as a cursor. Only callable by the admin account.
14
- abstract contract Destroy is CommandBase {
20
+ abstract contract Destroy is CommandBase, DestroyHook {
15
21
  uint internal immutable destroyId = commandId(NAME);
16
22
 
17
23
  constructor(string memory input) {
18
- emit Command(host, NAME, input, destroyId, State.Empty, State.Empty, false);
24
+ emit Command(host, destroyId, NAME, input, Keys.Empty, Keys.Empty, false);
19
25
  }
20
26
 
21
- /// @notice Override to run host teardown or destruction logic.
22
- /// @param input Cursor over the full request byte stream.
23
- function destroy(Cur memory input) internal virtual;
24
-
25
27
  function destroy(
26
28
  CommandContext calldata c
27
- ) external onlyAdmin(c.account) onlyCommand(destroyId, c.target) returns (bytes memory) {
29
+ ) external onlyAdmin(c.account) returns (bytes memory) {
28
30
  Cur memory input = cursor(c.request);
29
31
  destroy(input);
30
32
  return "";
@@ -0,0 +1,38 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import {CommandContext, CommandPayable, Keys} from "../Base.sol";
5
+ import {Cursors, Cur, Schemas} from "../../Cursors.sol";
6
+ import {Budget, Values} from "../../utils/Value.sol";
7
+ import {Ids} from "../../utils/Ids.sol";
8
+
9
+ using Cursors for Cur;
10
+
11
+ string constant NAME = "executePayable";
12
+
13
+ /// @title ExecutePayable
14
+ /// @notice Admin command that forwards raw calldata to one or more target nodes.
15
+ /// Each CALL block specifies a target node ID, native value, and raw calldata payload.
16
+ /// Only callable by the admin account.
17
+ abstract contract ExecutePayable is CommandPayable {
18
+ uint internal immutable executePayableId = commandId(NAME);
19
+
20
+ constructor() {
21
+ emit Command(host, executePayableId, NAME, Schemas.Call, Keys.Empty, Keys.Empty, true);
22
+ }
23
+
24
+ function executePayable(CommandContext calldata c) external payable onlyAdmin(c.account) returns (bytes memory) {
25
+ (Cur memory request, , ) = cursor(c.request, 1);
26
+ Budget memory budget = Values.fromMsg();
27
+
28
+ while (request.i < request.bound) {
29
+ (uint target, uint value, bytes calldata data) = request.unpackCall();
30
+ address addr = Ids.nodeAddr(target);
31
+ callAddr(addr, Values.use(budget, value), data);
32
+ }
33
+
34
+ request.complete();
35
+ settleValue(c.account, budget);
36
+ return "";
37
+ }
38
+ }
@@ -1,30 +1,32 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import { CommandBase, CommandContext, State } from "../Base.sol";
4
+ import { CommandBase, CommandContext, Keys } from "../Base.sol";
5
5
  import { Cursors, Cur } from "../../Cursors.sol";
6
6
 
7
7
  string constant NAME = "init";
8
8
 
9
9
  using Cursors for Cur;
10
10
 
11
+ abstract contract InitHook {
12
+ /// @notice Override to run host initialization logic.
13
+ /// @param input Cursor over the full request byte stream.
14
+ function init(Cur memory input) internal virtual;
15
+ }
16
+
11
17
  /// @title Init
12
18
  /// @notice Admin command that runs host initialization logic via a virtual hook.
13
19
  /// The full request is passed to `init` as a cursor. Only callable by the admin account.
14
- abstract contract Init is CommandBase {
20
+ abstract contract Init is CommandBase, InitHook {
15
21
  uint internal immutable initId = commandId(NAME);
16
22
 
17
23
  constructor(string memory input) {
18
- emit Command(host, NAME, input, initId, State.Empty, State.Empty, false);
24
+ emit Command(host, initId, NAME, input, Keys.Empty, Keys.Empty, false);
19
25
  }
20
26
 
21
- /// @notice Override to run host initialization logic.
22
- /// @param input Cursor over the full request byte stream.
23
- function init(Cur memory input) internal virtual;
24
-
25
27
  function init(
26
28
  CommandContext calldata c
27
- ) external onlyAdmin(c.account) onlyCommand(initId, c.target) returns (bytes memory) {
29
+ ) external onlyAdmin(c.account) returns (bytes memory) {
28
30
  Cur memory input = cursor(c.request);
29
31
  init(input);
30
32
  return "";
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import { CommandContext, CommandPayable, State } from "../Base.sol";
4
+ import { CommandContext, CommandPayable, Keys } from "../Base.sol";
5
5
  import { Cursors, Cur, Schemas } from "../../Cursors.sol";
6
6
  import { Budget, Values } from "../../utils/Value.sol";
7
7
  using Cursors for Cur;
@@ -10,23 +10,23 @@ string constant NAME = "relocatePayable";
10
10
 
11
11
  /// @title RelocatePayable
12
12
  /// @notice Admin command that forwards native value (ETH) to one or more destination hosts.
13
- /// Each FUNDING block in the request specifies a target host node ID and an amount to forward.
13
+ /// Each RELOCATION block in the request specifies a target host node ID and an amount to forward.
14
14
  /// Only callable by the admin account.
15
15
  abstract contract RelocatePayable is CommandPayable {
16
16
  uint internal immutable relocatePayableId = commandId(NAME);
17
17
 
18
18
  constructor() {
19
- emit Command(host, NAME, Schemas.Funding, relocatePayableId, State.Empty, State.Empty, true);
19
+ emit Command(host, relocatePayableId, NAME, Schemas.Relocation, Keys.Empty, Keys.Empty, true);
20
20
  }
21
21
 
22
22
  function relocatePayable(
23
23
  CommandContext calldata c
24
- ) external payable onlyAdmin(c.account) onlyCommand(relocatePayableId, c.target) returns (bytes memory) {
24
+ ) external payable onlyAdmin(c.account) returns (bytes memory) {
25
25
  (Cur memory request, , ) = cursor(c.request, 1);
26
26
  Budget memory budget = Values.fromMsg();
27
27
 
28
28
  while (request.i < request.bound) {
29
- (uint peer, uint amount) = request.unpackFunding();
29
+ (uint peer, uint amount) = request.unpackRelocation();
30
30
  callTo(peer, Values.use(budget, amount), "");
31
31
  }
32
32
 
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import { CommandBase, CommandContext, State } from "../Base.sol";
4
+ import { CommandBase, CommandContext, Keys } from "../Base.sol";
5
5
  import { Cursors, Cur, Schemas } from "../../Cursors.sol";
6
6
  using Cursors for Cur;
7
7
 
@@ -15,17 +15,17 @@ abstract contract Unauthorize is CommandBase {
15
15
  uint internal immutable unauthorizeId = commandId(NAME);
16
16
 
17
17
  constructor() {
18
- emit Command(host, NAME, Schemas.Node, unauthorizeId, State.Empty, State.Empty, false);
18
+ emit Command(host, unauthorizeId, NAME, Schemas.Node, Keys.Empty, Keys.Empty, false);
19
19
  }
20
20
 
21
21
  function unauthorize(
22
22
  CommandContext calldata c
23
- ) external onlyAdmin(c.account) onlyCommand(unauthorizeId, c.target) returns (bytes memory) {
23
+ ) external onlyAdmin(c.account) returns (bytes memory) {
24
24
  (Cur memory request, , ) = cursor(c.request, 1);
25
25
 
26
26
  while (request.i < request.bound) {
27
27
  uint node = request.unpackNode();
28
- access(node, false);
28
+ unauthorize(node);
29
29
  }
30
30
 
31
31
  request.complete();
package/core/Access.sol CHANGED
@@ -1,46 +1,53 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import { AccessEvent } from "../events/Access.sol";
5
- import { HostBound } from "./HostBound.sol";
6
- import { Accounts } from "../utils/Accounts.sol";
7
- import { Ids } from "../utils/Ids.sol";
8
- import { addrOr } from "../utils/Utils.sol";
4
+ import {AccessEvent} from "../events/Access.sol";
5
+ import {RootZeroContext} from "./Context.sol";
6
+ import {Accounts} from "../utils/Accounts.sol";
7
+ import {Ids} from "../utils/Ids.sol";
8
+ import {addrOr} from "../utils/Utils.sol";
9
9
 
10
10
  /// @title AccessControl
11
11
  /// @notice Host access control layer.
12
12
  /// Tracks an immutable trusted commander, the host's own node ID, and a
13
- /// mapping of externally authorized node IDs. Inbound trust is host-based:
14
- /// authorized hosts, the commander, and this contract itself may interact
13
+ /// mapping of externally trusted node IDs. Inbound trust is host-based:
14
+ /// trusted hosts, the commander, and this contract itself may interact
15
15
  /// with the host through the guarded command and peer entrypoints.
16
- abstract contract AccessControl is HostBound, AccessEvent {
16
+ abstract contract AccessControl is RootZeroContext, AccessEvent {
17
17
  /// @dev Trusted commander address. All calls from this address are implicitly trusted.
18
18
  /// Defaults to `address(this)` when no external commander is provided.
19
19
  address internal immutable commander;
20
20
  /// @dev Admin account ID derived from the commander address at construction time.
21
21
  bytes32 internal immutable adminAccount;
22
22
 
23
- /// @dev Mapping from node ID to authorization status.
24
- /// Authorised nodes may interact with the host as trusted callers or call targets.
25
- mapping(uint => bool) internal authorized;
23
+ /// @dev Mapping from node ID to trust status.
24
+ mapping(uint node => bool) internal trusted;
26
25
 
27
- /// @dev Thrown when `ensureTrusted` is called with a node that is not authorized.
28
- error UnauthorizedNode(uint node);
29
26
  /// @dev Thrown when `enforceCaller` is called by an address that is not trusted.
30
27
  error UnauthorizedCaller(address addr);
31
28
 
29
+ /// @dev Thrown when a required trusted node is missing from the trusted set.
30
+ error UnauthorizedNode(uint node);
31
+
32
32
  constructor(address cmdr) {
33
33
  commander = addrOr(cmdr, address(this));
34
34
  adminAccount = Accounts.toAdmin(commander);
35
35
  }
36
36
 
37
- /// @notice Grant or revoke authorization for a node.
38
- /// Inbound authentication is host-based: the node ID used here should be a host ID.
39
- /// @param node Node ID to authorize or deauthorize.
40
- /// @param allow True to grant authorization, false to revoke it.
41
- function access(uint node, bool allow) internal {
42
- authorized[node] = allow;
43
- emit Access(host, node, allow);
37
+ /// @notice Grant authorization for a node.
38
+ /// Accepts any node ID that should be trusted by this contract.
39
+ /// @param node Node ID to authorize.
40
+ function authorize(uint node) internal {
41
+ trusted[node] = true;
42
+ emit Access(host, node, true);
43
+ }
44
+
45
+ /// @notice Revoke authorization for a node.
46
+ /// Accepts any node ID that should no longer be trusted by this contract.
47
+ /// @param node Node ID to unauthorize.
48
+ function unauthorize(uint node) internal {
49
+ trusted[node] = false;
50
+ emit Access(host, node, false);
44
51
  }
45
52
 
46
53
  /// @notice Return true if `caller` is an implicitly trusted address.
@@ -48,7 +55,17 @@ abstract contract AccessControl is HostBound, AccessEvent {
48
55
  /// whose host ID has been explicitly authorized.
49
56
  /// @param caller Address to check.
50
57
  function isTrusted(address caller) internal view returns (bool) {
51
- return caller == commander || caller == address(this) || authorized[Ids.toHost(caller)];
58
+ return caller == commander || caller == address(this) || trusted[Ids.toHost(caller)];
59
+ }
60
+
61
+ /// @notice Assert that `node` is in the trusted set and return it.
62
+ /// @param node Node ID to validate.
63
+ /// @return The same `node` value if trusted.
64
+ function ensureTrusted(uint node) internal view returns (uint) {
65
+ if (node == 0 || !trusted[node]) {
66
+ revert UnauthorizedNode(node);
67
+ }
68
+ return node;
52
69
  }
53
70
 
54
71
  /// @notice Assert that `caller` is trusted and return it.
@@ -61,17 +78,4 @@ abstract contract AccessControl is HostBound, AccessEvent {
61
78
  }
62
79
  return caller;
63
80
  }
64
-
65
- /// @notice Assert that `node` is in the authorized set and return it.
66
- /// Used for outbound trust checks before calling another node.
67
- /// Accepts any authorized node ID (host or command).
68
- /// Inbound caller authentication is host-only via `enforceCaller(msg.sender)`.
69
- /// @param node Node ID to validate.
70
- /// @return The same `node` value if authorized.
71
- function ensureTrusted(uint node) internal view returns (uint) {
72
- if (node == 0 || !authorized[node]) {
73
- revert UnauthorizedNode(node);
74
- }
75
- return node;
76
- }
77
81
  }
package/core/Balances.sol CHANGED
@@ -7,34 +7,33 @@ import {BalanceEvent} from "../events/Balance.sol";
7
7
  error InsufficientFunds();
8
8
 
9
9
  /// @title Balances
10
- /// @notice On-chain ledger for per-account, per-asset token balances.
11
- /// Balances are keyed by `(account, assetKey)` where `assetKey` is the
12
- /// value returned by `Assets.key(asset, meta)`.
10
+ /// @notice On-chain ledger for per-account, per-slot balances.
11
+ /// Higher-level modules decide how slots are derived and validated.
13
12
  abstract contract Balances is BalanceEvent {
14
- /// @dev account -> assetKey -> balance (in the asset's native units).
15
- mapping(bytes32 account => mapping(bytes32 assetKey => uint amount)) internal balances;
13
+ /// @dev account -> slot -> balance.
14
+ mapping(bytes32 account => mapping(bytes32 slot => uint amount)) internal balances;
15
+
16
+ /// @notice Add `amount` to an account balance and return the new balance.
17
+ /// @param account Account identifier.
18
+ /// @param slot Storage slot for the position being credited.
19
+ /// @param amount Amount to credit.
20
+ /// @return balance New balance after the credit.
21
+ function creditTo(bytes32 account, bytes32 slot, uint amount) internal returns (uint balance) {
22
+ balance = balances[account][slot] += amount;
23
+ }
16
24
 
17
25
  /// @notice Deduct `amount` from an account balance and return the new balance.
18
26
  /// Reverts with `InsufficientFunds` if the current balance is less than `amount`.
19
27
  /// @param account Account identifier.
20
- /// @param assetKey Storage key for the (asset, meta) pair.
28
+ /// @param slot Storage slot for the position being debited.
21
29
  /// @param amount Amount to deduct.
22
30
  /// @return balance New balance after the debit.
23
- function debitFrom(bytes32 account, bytes32 assetKey, uint amount) internal returns (uint balance) {
24
- balance = balances[account][assetKey];
31
+ function debitFrom(bytes32 account, bytes32 slot, uint amount) internal returns (uint balance) {
32
+ balance = balances[account][slot];
25
33
  if (balance < amount) revert InsufficientFunds();
26
34
  unchecked {
27
35
  balance -= amount;
28
36
  }
29
- balances[account][assetKey] = balance;
30
- }
31
-
32
- /// @notice Add `amount` to an account balance and return the new balance.
33
- /// @param account Account identifier.
34
- /// @param assetKey Storage key for the (asset, meta) pair.
35
- /// @param amount Amount to credit.
36
- /// @return balance New balance after the credit.
37
- function creditTo(bytes32 account, bytes32 assetKey, uint amount) internal returns (uint balance) {
38
- balance = balances[account][assetKey] += amount;
37
+ balances[account][slot] = balance;
39
38
  }
40
39
  }
@@ -1,9 +1,8 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import { AccessControl } from "./Access.sol";
5
- import { CursorBase } from "./CursorBase.sol";
6
- import { Ids } from "../utils/Ids.sol";
4
+ import {AccessControl} from "./Access.sol";
5
+ import {Ids} from "../utils/Ids.sol";
7
6
 
8
7
  /// @dev Emitted when a trusted inter-node call fails.
9
8
  /// @param addr Contract address that was called.
@@ -11,11 +10,9 @@ import { Ids } from "../utils/Ids.sol";
11
10
  /// @param err Revert data returned by the failed call.
12
11
  error FailedCall(address addr, bytes4 selector, bytes err);
13
12
 
14
- /// @title OperationBase
15
- /// @notice Shared base for command and peer contracts.
16
- /// Provides convenience wrappers for cursor construction, quotient validation,
17
- /// and trusted inter-node calls. Inherits access control from `AccessControl`.
18
- abstract contract OperationBase is CursorBase, AccessControl {
13
+ /// @title NodeCalls
14
+ /// @notice Shared trusted inter-node call helpers for contracts that can talk to other nodes.
15
+ abstract contract NodeCalls is AccessControl {
19
16
  /// @notice Return the host node ID corresponding to the current caller.
20
17
  /// @dev Encodes `msg.sender` as a host ID using the local-chain host layout.
21
18
  /// @return Host node ID for `msg.sender`.
@@ -1,13 +1,20 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import { Cur, Cursors } from "../Cursors.sol";
4
+ import {Cur, Cursors} from "../Cursors.sol";
5
+ import {Assets} from "../utils/Assets.sol";
6
+ import {Ids} from "../utils/Ids.sol";
5
7
 
6
8
  using Cursors for Cur;
7
9
 
8
- /// @title CursorBase
9
- /// @notice Shared cursor convenience helpers for contracts that consume block streams.
10
- abstract contract CursorBase {
10
+ /// @title RootZeroContext
11
+ /// @notice Shared rootzero contract context for host identity, native value identity, and block-stream cursors.
12
+ abstract contract RootZeroContext {
13
+ /// @dev This contract's host node ID, set to `Ids.toHost(address(this))` at construction.
14
+ uint public immutable host = Ids.toHost(address(this));
15
+ /// @dev Asset ID for the native chain value (ETH), bound to the current chain at deployment.
16
+ bytes32 internal immutable valueAsset = Assets.toValue();
17
+
11
18
  /// @notice Open a cursor over a calldata block stream.
12
19
  /// @param source Calldata slice to parse.
13
20
  /// @return cur Cursor positioned at the beginning of `source`.
@@ -39,5 +46,4 @@ abstract contract CursorBase {
39
46
  (, , uint quotient) = cur.primeRun(group);
40
47
  if (quotient != expectedQuotient) revert Cursors.BadRatio();
41
48
  }
42
-
43
49
  }
package/core/Host.sol CHANGED
@@ -1,13 +1,14 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import { AccessControl } from "./Access.sol";
5
- import { Authorize } from "../commands/admin/Authorize.sol";
6
- import { Unauthorize } from "../commands/admin/Unauthorize.sol";
7
- import { RelocatePayable } from "../commands/admin/Relocate.sol";
8
- import { HostAnnouncedEvent } from "../events/Host.sol";
9
- import { IHostDiscovery } from "../interfaces/IHostDiscovery.sol";
10
- import { Ids } from "../utils/Ids.sol";
4
+ import {AccessControl} from "./Access.sol";
5
+ import {Authorize} from "../commands/admin/Authorize.sol";
6
+ import {Unauthorize} from "../commands/admin/Unauthorize.sol";
7
+ import {ExecutePayable} from "../commands/admin/Execute.sol";
8
+ import {RelocatePayable} from "../commands/admin/Relocate.sol";
9
+ import {HostAnnouncedEvent} from "../events/Host.sol";
10
+ import {IHostDiscovery} from "../interfaces/IHostDiscovery.sol";
11
+ import {Ids} from "../utils/Ids.sol";
11
12
 
12
13
  /// @notice Mixin that allows a contract to act as a host discovery registry.
13
14
  /// Hosts call `announceHost` on a discovery contract to register themselves.
@@ -19,16 +20,16 @@ abstract contract HostDiscovery is HostAnnouncedEvent, IHostDiscovery {
19
20
  /// @param version Protocol version the host implements.
20
21
  /// @param namespace Human-readable namespace string for the host.
21
22
  function announceHost(uint id, uint blocknum, uint16 version, string calldata namespace) external {
22
- emit HostAnnounced(Ids.host(id, msg.sender), blocknum, version, namespace);
23
+ emit HostAnnounced(Ids.matchHost(id, msg.sender), blocknum, version, namespace);
23
24
  }
24
25
  }
25
26
 
26
27
  /// @title Host
27
28
  /// @notice Abstract base contract for rootzero host implementations.
28
- /// Inherits admin command support (authorize, unauthorize, relocatePayable) and
29
+ /// Inherits admin command support (authorize, unauthorize, executePayable, relocatePayable) and
29
30
  /// optionally announces itself to a discovery contract at deployment.
30
31
  /// Accepts native ETH payments via the `receive` function.
31
- abstract contract Host is Authorize, Unauthorize, RelocatePayable {
32
+ abstract contract Host is Authorize, Unauthorize, ExecutePayable, RelocatePayable {
32
33
  /// @param cmdr Commander address; passed to `AccessControl`.
33
34
  /// If `cmdr` is a deployed contract, the host calls `announceHost`
34
35
  /// on it during construction to register with the discovery registry.