@rootzero/contracts 0.9.6 → 0.9.7

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.
@@ -1,12 +1,15 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- // Aggregator: re-exports command, admin, and peer abstractions.
5
- // Import this file to inherit from the full rootzero command surface without managing individual paths.
4
+ // Aggregator: re-exports command, admin, peer, guard, and query endpoint abstractions.
5
+ // Import this file to inherit from the full rootzero callable host surface without managing individual paths.
6
6
 
7
+ // Shared helpers
8
+ import { Keys } from "./blocks/Keys.sol";
7
9
  import { CommandBase, CommandContext } from "./commands/Base.sol";
8
10
  import { Payable } from "./core/Payable.sol";
9
- import { Keys } from "./blocks/Keys.sol";
11
+
12
+ // Commands
10
13
  import { Burn, BurnHook } from "./commands/Burn.sol";
11
14
  import { CreditAccount, CreditAccountHook } from "./commands/Credit.sol";
12
15
  import { DebitAccount, DebitAccountHook } from "./commands/Debit.sol";
@@ -14,22 +17,34 @@ import { Deposit, DepositHook, DepositPayable, DepositPayableHook } from "./comm
14
17
  import { Provision, ProvisionHook, ProvisionPayable, ProvisionPayableHook } from "./commands/Provision.sol";
15
18
  import { Transfer, TransferHook } from "./commands/Transfer.sol";
16
19
  import { Withdraw, WithdrawHook } from "./commands/Withdraw.sol";
20
+
21
+ // Admin commands
17
22
  import { AllowAssets, AllowAssetsHook } from "./commands/admin/AllowAssets.sol";
18
- import { Destroy, DestroyHook } from "./commands/admin/Destroy.sol";
19
- import { ExecutePayable } from "./commands/admin/Execute.sol";
23
+ import { Allowance, AllowanceHook } from "./commands/admin/Allowance.sol";
24
+ import { Appoint } from "./commands/admin/Appoint.sol";
20
25
  import { Authorize } from "./commands/admin/Authorize.sol";
26
+ import { Destroy, DestroyHook } from "./commands/admin/Destroy.sol";
21
27
  import { DenyAssets, DenyAssetsHook } from "./commands/admin/DenyAssets.sol";
28
+ import { Dismiss } from "./commands/admin/Dismiss.sol";
29
+ import { ExecutePayable } from "./commands/admin/Execute.sol";
22
30
  import { Init, InitHook } from "./commands/admin/Init.sol";
23
- import { Allowance, AllowanceHook } from "./commands/admin/Allowance.sol";
24
31
  import { Unauthorize } from "./commands/admin/Unauthorize.sol";
32
+
33
+ // Peer endpoints
25
34
  import { PeerBase, encodePeerCall } from "./peer/Base.sol";
35
+ import { PeerAllowAssets } from "./peer/AllowAssets.sol";
26
36
  import { PeerAllowance } from "./peer/Allowance.sol";
27
37
  import { PeerBalancePull, BalancePullHook } from "./peer/BalancePull.sol";
28
- import { PeerAllowAssets } from "./peer/AllowAssets.sol";
29
38
  import { PeerDenyAssets } from "./peer/DenyAssets.sol";
30
39
  import { PeerPipePayable } from "./peer/Pipe.sol";
31
40
  import { PeerSettle } from "./peer/Settle.sol";
32
41
 
42
+ // Guard endpoints
43
+ import { GuardBase, encodeGuardCall } from "./guards/Base.sol";
44
+ import { Revoke } from "./guards/Revoke.sol";
33
45
 
34
-
35
-
46
+ // Query endpoints
47
+ import { QueryBase, encodeQueryCall } from "./queries/Base.sol";
48
+ import { AssetStatus, AssetStatusHook } from "./queries/Assets.sol";
49
+ import { GetBalances, GetBalancesHook } from "./queries/Balances.sol";
50
+ import { GetPosition, GetPositionHook } from "./queries/Positions.sol";
package/Events.sol CHANGED
@@ -4,7 +4,6 @@ pragma solidity ^0.8.33;
4
4
  // Aggregator: re-exports all event contracts.
5
5
  // Import this file to get access to every event emitter in one import.
6
6
 
7
- import { AccessEvent } from "./events/Access.sol";
8
7
  import { AdminEvent } from "./events/Admin.sol";
9
8
  import { AssetStatusEvent } from "./events/Asset.sol";
10
9
  import { BalanceEvent } from "./events/Balance.sol";
@@ -15,7 +14,10 @@ import { DebtEvent } from "./events/Debt.sol";
15
14
  import { PositionEvent } from "./events/Position.sol";
16
15
  import { ReceivedEvent } from "./events/Received.sol";
17
16
  import { EventEmitter } from "./events/Emitter.sol";
17
+ import { GuardEvent } from "./events/Guard.sol";
18
+ import { GuardianEvent } from "./events/Guardian.sol";
18
19
  import { IntroductionEvent } from "./events/Introduction.sol";
20
+ import { NodeEvent } from "./events/Node.sol";
19
21
  import { PeerEvent } from "./events/Peer.sol";
20
22
  import { QueryEvent } from "./events/Query.sol";
21
23
  import { RootedEvent } from "./events/Rooted.sol";
package/README.md CHANGED
@@ -9,8 +9,7 @@ It contains the reusable contracts, utilities, cursor parsers, and encoding help
9
9
  Most consumers should start from the package root entry points:
10
10
 
11
11
  - `@rootzero/contracts/Core.sol` — host, access control, balances, and validator building blocks
12
- - `@rootzero/contracts/Commands.sol` — command and peer base contracts plus all standard command mixins
13
- - `@rootzero/contracts/Queries.sol` — query base contracts plus standard query mixins
12
+ - `@rootzero/contracts/Endpoints.sol` — command, peer, guard, and query base contracts plus standard endpoint mixins
14
13
  - `@rootzero/contracts/Cursors.sol` — cursor reader (`Cur`), block schemas, key constants, typed block helpers, and writers
15
14
  - `@rootzero/contracts/Utils.sol` — IDs, assets, accounts, layout, and value helpers
16
15
  - `@rootzero/contracts/Events.sol` — reusable event emitters and event contracts
@@ -66,7 +65,7 @@ Extend `CommandBase` to define a command mixin that runs inside the protocol's t
66
65
  // SPDX-License-Identifier: GPL-3.0-only
67
66
  pragma solidity ^0.8.33;
68
67
 
69
- import { CommandBase, CommandContext, Keys } from "@rootzero/contracts/Commands.sol";
68
+ import { CommandBase, CommandContext, Keys } from "@rootzero/contracts/Endpoints.sol";
70
69
  import { Cursors, Cur, Schemas, Writer, Writers } from "@rootzero/contracts/Cursors.sol";
71
70
 
72
71
  using Cursors for Cur;
package/commands/Base.sol CHANGED
@@ -23,12 +23,10 @@ struct CommandContext {
23
23
  abstract contract CommandBase is NodeCalls, CommandEvent {
24
24
  /// @dev Thrown when `onlyActive` finds that `deadline` has already passed.
25
25
  error Expired();
26
- /// @dev Thrown when `onlyAdmin` finds that `account` is not the admin account.
27
- error NotAdmin();
28
26
 
29
27
  /// @dev Restrict execution to the commander using the host's admin account.
30
28
  modifier onlyAdmin(bytes32 account) {
31
- if (account != adminAccount) revert NotAdmin();
29
+ if (account != admin) revert AccessDenied();
32
30
  enforceCommander(msg.sender);
33
31
  _;
34
32
  }
@@ -39,12 +37,6 @@ abstract contract CommandBase is NodeCalls, CommandEvent {
39
37
  _;
40
38
  }
41
39
 
42
- /// @dev Restrict execution to callers whose host node is trusted.
43
- modifier onlyTrusted() {
44
- enforceCaller(msg.sender);
45
- _;
46
- }
47
-
48
40
  /// @dev Restrict execution to invocations where `deadline` is in the future.
49
41
  /// @param deadline Unix timestamp after which the invocation is considered expired.
50
42
  modifier onlyActive(uint deadline) {
@@ -0,0 +1,35 @@
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
+ import { AdminEvent } from "../../events/Admin.sol";
7
+ using Cursors for Cur;
8
+
9
+ /// @title Appoint
10
+ /// @notice Admin command that grants guardian status to a list of account IDs.
11
+ /// Each ACCOUNT block in the request is enabled as a guardian on the host.
12
+ /// Only callable by the admin account.
13
+ abstract contract Appoint is CommandBase, AdminEvent {
14
+ string private constant NAME = "appoint";
15
+
16
+ uint internal immutable appointId = commandId(NAME);
17
+
18
+ constructor() {
19
+ emit Admin(host, appointId, NAME, "1:0:0", Schemas.Account, Keys.Empty, Keys.Empty, false);
20
+ }
21
+
22
+ function appoint(
23
+ CommandContext calldata c
24
+ ) external onlyAdmin(c.account) returns (bytes memory) {
25
+ (Cur memory request, ) = cursor(c.request, 1);
26
+
27
+ while (request.i < request.bound) {
28
+ bytes32 account = request.unpackAccount();
29
+ setGuardian(account, true);
30
+ }
31
+
32
+ request.close();
33
+ return "";
34
+ }
35
+ }
@@ -26,7 +26,7 @@ abstract contract Authorize is CommandBase, AdminEvent {
26
26
 
27
27
  while (request.i < request.bound) {
28
28
  uint node = request.unpackNode();
29
- authorize(node);
29
+ setNode(node, true);
30
30
  }
31
31
 
32
32
  request.close();
@@ -0,0 +1,35 @@
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
+ import { AdminEvent } from "../../events/Admin.sol";
7
+ using Cursors for Cur;
8
+
9
+ /// @title Dismiss
10
+ /// @notice Admin command that revokes guardian status from a list of account IDs.
11
+ /// Each ACCOUNT block in the request is disabled as a guardian on the host.
12
+ /// Only callable by the admin account.
13
+ abstract contract Dismiss is CommandBase, AdminEvent {
14
+ string private constant NAME = "dismiss";
15
+
16
+ uint internal immutable dismissId = commandId(NAME);
17
+
18
+ constructor() {
19
+ emit Admin(host, dismissId, NAME, "1:0:0", Schemas.Account, Keys.Empty, Keys.Empty, false);
20
+ }
21
+
22
+ function dismiss(
23
+ CommandContext calldata c
24
+ ) external onlyAdmin(c.account) returns (bytes memory) {
25
+ (Cur memory request, ) = cursor(c.request, 1);
26
+
27
+ while (request.i < request.bound) {
28
+ bytes32 account = request.unpackAccount();
29
+ setGuardian(account, false);
30
+ }
31
+
32
+ request.close();
33
+ return "";
34
+ }
35
+ }
@@ -26,7 +26,7 @@ abstract contract Unauthorize is CommandBase, AdminEvent {
26
26
 
27
27
  while (request.i < request.bound) {
28
28
  uint node = request.unpackNode();
29
- unauthorize(node);
29
+ setNode(node, false);
30
30
  }
31
31
 
32
32
  request.close();
package/core/Access.sol CHANGED
@@ -1,7 +1,8 @@
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";
4
+ import {NodeEvent} from "../events/Node.sol";
5
+ import {GuardianEvent} from "../events/Guardian.sol";
5
6
  import {RootZeroContext} from "./Context.sol";
6
7
  import {Accounts} from "../utils/Accounts.sol";
7
8
  import {Ids} from "../utils/Ids.sol";
@@ -13,41 +14,48 @@ import {addrOr} from "../utils/Utils.sol";
13
14
  /// mapping of externally trusted node IDs. Inbound trust is host-based:
14
15
  /// trusted hosts, the commander, and this contract itself may interact
15
16
  /// with the host through the guarded command and peer entrypoints.
16
- abstract contract AccessControl is RootZeroContext, AccessEvent {
17
+ abstract contract AccessControl is RootZeroContext, NodeEvent, GuardianEvent {
17
18
  /// @dev Trusted commander address. All calls from this address are implicitly trusted.
18
19
  /// Defaults to `address(this)` when no external commander is provided.
19
20
  address internal immutable commander;
20
21
  /// @dev Admin account ID derived from the commander address at construction time.
21
- bytes32 internal immutable adminAccount;
22
+ bytes32 internal immutable admin;
22
23
 
23
- /// @dev Mapping from node ID to trust status.
24
- mapping(uint node => bool) internal trusted;
24
+ /// @dev Mapping from guardian account ID to guardian status.
25
+ mapping(bytes32 account => bool) internal guardians;
25
26
 
26
- /// @dev Thrown when `enforceCaller` is called by an address that is not trusted.
27
- error UnauthorizedCaller(address addr);
27
+ /// @dev Mapping from node ID to trust status.
28
+ mapping(uint node => bool) internal nodes;
28
29
 
29
- /// @dev Thrown when a required trusted node is missing from the trusted set.
30
- error UnauthorizedNode(uint node);
30
+ /// @dev Thrown when a caller, account, or node lacks required access.
31
+ error AccessDenied();
31
32
 
32
33
  constructor(address cmdr) {
33
34
  commander = addrOr(cmdr, address(this));
34
- adminAccount = Accounts.toAdmin(commander);
35
+ admin = Accounts.toAdmin(commander);
36
+ }
37
+
38
+ /// @notice Set authorization status for a node.
39
+ /// @param node Node ID to update.
40
+ /// @param active True to authorize the node, false to revoke it.
41
+ function setNode(uint node, bool active) internal {
42
+ nodes[node] = active;
43
+ emit Node(host, node, active);
35
44
  }
36
45
 
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);
46
+ /// @notice Set guardian status for an account.
47
+ /// @param account Guardian account ID to update.
48
+ /// @param active True to enable the guardian, false to revoke it.
49
+ function setGuardian(bytes32 account, bool active) internal {
50
+ Accounts.guardian(account);
51
+ guardians[account] = active;
52
+ emit Guardian(host, account, active);
43
53
  }
44
54
 
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);
55
+ /// @notice Return true if `addr` is an active guardian.
56
+ /// @param addr EVM address to check as a guardian account.
57
+ function isGuardian(address addr) internal view returns (bool) {
58
+ return guardians[Accounts.toGuardian(addr)];
51
59
  }
52
60
 
53
61
  /// @notice Return true if `caller` is an implicitly trusted address.
@@ -55,15 +63,15 @@ abstract contract AccessControl is RootZeroContext, AccessEvent {
55
63
  /// whose host ID has been explicitly authorized.
56
64
  /// @param caller Address to check.
57
65
  function isTrusted(address caller) internal view returns (bool) {
58
- return caller == commander || caller == address(this) || trusted[Ids.toHost(caller)];
66
+ return caller == commander || caller == address(this) || nodes[Ids.toHost(caller)];
59
67
  }
60
68
 
61
69
  /// @notice Assert that `node` is in the trusted set and return it.
62
70
  /// @param node Node ID to validate.
63
71
  /// @return The same `node` value if trusted.
64
72
  function ensureTrusted(uint node) internal view returns (uint) {
65
- if (node == 0 || !trusted[node]) {
66
- revert UnauthorizedNode(node);
73
+ if (node == 0 || !nodes[node]) {
74
+ revert AccessDenied();
67
75
  }
68
76
  return node;
69
77
  }
@@ -74,7 +82,7 @@ abstract contract AccessControl is RootZeroContext, AccessEvent {
74
82
  /// @return The same `caller` value if trusted.
75
83
  function enforceCaller(address caller) internal view returns (address) {
76
84
  if (caller == address(0) || !isTrusted(caller)) {
77
- revert UnauthorizedCaller(caller);
85
+ revert AccessDenied();
78
86
  }
79
87
  return caller;
80
88
  }
@@ -85,7 +93,7 @@ abstract contract AccessControl is RootZeroContext, AccessEvent {
85
93
  /// @return The same `caller` value if it is the commander.
86
94
  function enforceCommander(address caller) internal view returns (address) {
87
95
  if (caller == address(0) || caller != commander) {
88
- revert UnauthorizedCaller(caller);
96
+ revert AccessDenied();
89
97
  }
90
98
  return caller;
91
99
  }
package/core/Host.sol CHANGED
@@ -2,7 +2,9 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import {AccessControl} from "./Access.sol";
5
+ import {Appoint} from "../commands/admin/Appoint.sol";
5
6
  import {Authorize} from "../commands/admin/Authorize.sol";
7
+ import {Dismiss} from "../commands/admin/Dismiss.sol";
6
8
  import {Unauthorize} from "../commands/admin/Unauthorize.sol";
7
9
  import {ExecutePayable} from "../commands/admin/Execute.sol";
8
10
  import {IntroductionEvent} from "../events/Introduction.sol";
@@ -24,7 +26,7 @@ interface IHostIntroduction {
24
26
  /// Inherits admin command support (authorize, unauthorize, executePayable) and
25
27
  /// optionally introduces itself to a commander host at deployment.
26
28
  /// Accepts native ETH payments via the `receive` function.
27
- abstract contract Host is Authorize, Unauthorize, ExecutePayable, IntroductionEvent, IHostIntroduction {
29
+ abstract contract Host is Authorize, Unauthorize, Appoint, Dismiss, ExecutePayable, IntroductionEvent, IHostIntroduction {
28
30
  /// @param cmdr Commander address; passed to `AccessControl`.
29
31
  /// If `cmdr` is a deployed contract, the host calls `introduce`
30
32
  /// on it during construction.
@@ -0,0 +1,19 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import {EventEmitter} from "./Emitter.sol";
5
+
6
+ /// @notice Emitted once per guard action during host deployment to publish its request schema.
7
+ abstract contract GuardEvent is EventEmitter {
8
+ string private constant ABI = "event Guard(uint indexed host, uint id, string name, string request)";
9
+
10
+ /// @param host Host node ID that owns this guard action.
11
+ /// @param id Guard action node ID.
12
+ /// @param name Human-readable guard action name.
13
+ /// @param request Schema DSL string describing the guard action request shape.
14
+ event Guard(uint indexed host, uint id, string name, string request);
15
+
16
+ constructor() {
17
+ emit EventAbi(ABI);
18
+ }
19
+ }
@@ -0,0 +1,18 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import { EventEmitter } from "./Emitter.sol";
5
+
6
+ /// @notice Emitted when a guardian account status changes on a host.
7
+ abstract contract GuardianEvent is EventEmitter {
8
+ string private constant ABI = "event Guardian(uint indexed host, bytes32 account, bool active)";
9
+
10
+ /// @param host Host node ID where the guardian change occurred.
11
+ /// @param account Guardian account ID.
12
+ /// @param active True if the guardian is enabled, false if revoked.
13
+ event Guardian(uint indexed host, bytes32 account, bool active);
14
+
15
+ constructor() {
16
+ emit EventAbi(ABI);
17
+ }
18
+ }
@@ -0,0 +1,18 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import { EventEmitter } from "./Emitter.sol";
5
+
6
+ /// @notice Emitted when a node's authorization status changes on a host.
7
+ abstract contract NodeEvent is EventEmitter {
8
+ string private constant ABI = "event Node(uint indexed host, uint node, bool active)";
9
+
10
+ /// @param host Host node ID where the authorization change occurred.
11
+ /// @param node Node ID that was authorized or revoked.
12
+ /// @param active True if the node is authorized, false if revoked.
13
+ event Node(uint indexed host, uint node, bool active);
14
+
15
+ constructor() {
16
+ emit EventAbi(ABI);
17
+ }
18
+ }
@@ -0,0 +1,35 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import {AccessControl} from "../core/Access.sol";
5
+ import {GuardEvent} from "../events/Guard.sol";
6
+ import {Ids, Selectors} from "../utils/Ids.sol";
7
+
8
+ /// @notice ABI-encode a guard action call from a target guard ID and request block stream.
9
+ /// @dev Derives the function selector from `target` via `Ids.guardSelector(target)`.
10
+ /// Reverts if `target` is not a valid guard ID.
11
+ /// @param target Destination guard action node ID embedding the target selector.
12
+ /// @param request Input block stream for the guard invocation.
13
+ /// @return ABI-encoded calldata for the guard action entry point.
14
+ function encodeGuardCall(uint target, bytes calldata request) pure returns (bytes memory) {
15
+ bytes4 selector = Ids.guardSelector(target);
16
+ return abi.encodeWithSelector(selector, request);
17
+ }
18
+
19
+ /// @title GuardBase
20
+ /// @notice Abstract base for guardian-only direct host actions.
21
+ /// Guard actions are non-payable direct calls with no command context, state, or response.
22
+ abstract contract GuardBase is AccessControl, GuardEvent {
23
+ /// @dev Restrict execution to active guardian addresses.
24
+ modifier onlyGuardian() {
25
+ if (!isGuardian(msg.sender)) revert AccessDenied();
26
+ _;
27
+ }
28
+
29
+ /// @notice Derive the deterministic node ID for a named guard action on this contract.
30
+ /// @param name Guard action function name (without argument list).
31
+ /// @return Guard action node ID.
32
+ function guardId(string memory name) internal view returns (uint) {
33
+ return Ids.toGuard(Selectors.guard(name), address(this));
34
+ }
35
+ }
@@ -0,0 +1,31 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import {GuardBase} from "./Base.sol";
5
+ import {Cursors, Cur, Schemas} from "../Cursors.sol";
6
+ using Cursors for Cur;
7
+
8
+ /// @title Revoke
9
+ /// @notice Guardian action that quickly revokes authorization from a list of node IDs.
10
+ /// Each NODE block in the request is deauthorized on the host.
11
+ /// Only callable by active guardian addresses.
12
+ abstract contract Revoke is GuardBase {
13
+ string private constant NAME = "revoke";
14
+
15
+ uint internal immutable revokeId = guardId(NAME);
16
+
17
+ constructor() {
18
+ emit Guard(host, revokeId, NAME, Schemas.Node);
19
+ }
20
+
21
+ function revoke(bytes calldata request) external onlyGuardian {
22
+ (Cur memory input, ) = cursor(request, 1);
23
+
24
+ while (input.i < input.bound) {
25
+ uint node = input.unpackNode();
26
+ setNode(node, false);
27
+ }
28
+
29
+ input.close();
30
+ }
31
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rootzero/contracts",
3
- "version": "0.9.6",
3
+ "version": "0.9.7",
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",
@@ -8,8 +8,9 @@ import {isFamily, toLocalBase, toUnspecifiedBase} from "./Utils.sol";
8
8
  /// @notice Encoding and decoding helpers for 256-bit account identifiers.
9
9
  ///
10
10
  /// Account IDs embed a 4-byte type tag in bits [255:224]:
11
- /// - `Admin` — chain-local EVM address in bits [191:32]
12
- /// - `User` — chain-agnostic EVM address in bits [191:32]
11
+ /// - `Admin` — chain-local EVM address in bits [191:32]
12
+ /// - `Guardian` — chain-local EVM address in bits [191:32]
13
+ /// - `User` — chain-agnostic EVM address in bits [191:32]
13
14
  library Accounts {
14
15
  /// @dev Thrown when an account ID does not belong to the EVM family.
15
16
  error InvalidAccount();
@@ -18,6 +19,8 @@ library Accounts {
18
19
  uint24 constant Family = (uint24(Layout.Evm32) << 8) | uint24(Layout.Account);
19
20
  /// @dev Full 4-byte type prefix for admin accounts (chain-local EVM address).
20
21
  uint32 constant Admin = (uint32(Layout.Evm32) << 16) | (uint32(Layout.Account) << 8) | uint32(Layout.Admin);
22
+ /// @dev Full 4-byte type prefix for guardian accounts (chain-local EVM address).
23
+ uint32 constant Guardian = (uint32(Layout.Evm32) << 16) | (uint32(Layout.Account) << 8) | uint32(Layout.Guardian);
21
24
  /// @dev Full 4-byte type prefix for user accounts (chain-agnostic EVM address).
22
25
  uint32 constant User = (uint32(Layout.Evm32) << 16) | (uint32(Layout.Account) << 8) | uint32(Layout.User);
23
26
  /// @dev Full 4-byte type prefix for keccak accounts (opaque 28-byte hash).
@@ -40,6 +43,11 @@ library Accounts {
40
43
  return prefix(account) == Admin;
41
44
  }
42
45
 
46
+ /// @notice Return true if `account` is a guardian account.
47
+ function isGuardian(bytes32 account) internal pure returns (bool) {
48
+ return prefix(account) == Guardian;
49
+ }
50
+
43
51
  /// @notice Return true if `account` is a user account.
44
52
  function isUser(bytes32 account) internal pure returns (bool) {
45
53
  return prefix(account) == User;
@@ -49,6 +57,38 @@ library Accounts {
49
57
  return prefix(account) == Keccak;
50
58
  }
51
59
 
60
+ /// @notice Assert that `input` is an admin account and return it unchanged.
61
+ /// @param input Account identifier to validate.
62
+ /// @return account The same `input` if it is an admin account.
63
+ function admin(bytes32 input) internal pure returns (bytes32 account) {
64
+ if (!isAdmin(input)) revert InvalidAccount();
65
+ return input;
66
+ }
67
+
68
+ /// @notice Assert that `input` is a guardian account and return it unchanged.
69
+ /// @param input Account identifier to validate.
70
+ /// @return account The same `input` if it is a guardian account.
71
+ function guardian(bytes32 input) internal pure returns (bytes32 account) {
72
+ if (!isGuardian(input)) revert InvalidAccount();
73
+ return input;
74
+ }
75
+
76
+ /// @notice Assert that `input` is a user account and return it unchanged.
77
+ /// @param input Account identifier to validate.
78
+ /// @return account The same `input` if it is a user account.
79
+ function user(bytes32 input) internal pure returns (bytes32 account) {
80
+ if (!isUser(input)) revert InvalidAccount();
81
+ return input;
82
+ }
83
+
84
+ /// @notice Assert that `input` is a keccak account and return it unchanged.
85
+ /// @param input Account identifier to validate.
86
+ /// @return account The same `input` if it is a keccak account.
87
+ function keccak(bytes32 input) internal pure returns (bytes32 account) {
88
+ if (!isKeccak(input)) revert InvalidAccount();
89
+ return input;
90
+ }
91
+
52
92
  /// @notice Encode an EVM address as a chain-local admin account ID.
53
93
  /// @param addr EVM address to embed.
54
94
  /// @return Admin account ID bound to the current chain.
@@ -56,6 +96,13 @@ library Accounts {
56
96
  return bytes32(toLocalBase(Admin) | (uint(uint160(addr)) << 32));
57
97
  }
58
98
 
99
+ /// @notice Encode an EVM address as a chain-local guardian account ID.
100
+ /// @param addr EVM address to embed.
101
+ /// @return Guardian account ID bound to the current chain.
102
+ function toGuardian(address addr) internal view returns (bytes32) {
103
+ return bytes32(toLocalBase(Guardian) | (uint(uint160(addr)) << 32));
104
+ }
105
+
59
106
  /// @notice Encode an EVM address as a chain-agnostic user account ID.
60
107
  /// @param addr EVM address to embed.
61
108
  /// @return User account ID without a chain binding.
package/utils/Ids.sol CHANGED
@@ -26,6 +26,8 @@ library Ids {
26
26
  uint32 constant Peer = (uint32(Layout.Evm32) << 16) | (uint32(Layout.Node) << 8) | uint32(Layout.Peer);
27
27
  /// @dev Full 4-byte type prefix for query nodes.
28
28
  uint32 constant Query = (uint32(Layout.Evm32) << 16) | (uint32(Layout.Node) << 8) | uint32(Layout.Query);
29
+ /// @dev Full 4-byte type prefix for guard action nodes.
30
+ uint32 constant Guard = (uint32(Layout.Evm32) << 16) | (uint32(Layout.Node) << 8) | uint32(Layout.Guard);
29
31
 
30
32
  /// @notice Return true if `id` is a host node ID.
31
33
  function isHost(uint id) internal pure returns (bool) {
@@ -47,6 +49,11 @@ library Ids {
47
49
  return uint32(id >> 224) == Query;
48
50
  }
49
51
 
52
+ /// @notice Return true if `id` is a guard action node ID.
53
+ function isGuard(uint id) internal pure returns (bool) {
54
+ return uint32(id >> 224) == Guard;
55
+ }
56
+
50
57
  /// @notice Return true if `id` is a local node ID for this contract.
51
58
  function isLocalNode(uint id) internal view returns (bool) {
52
59
  return isLocalFamily(id, Node) && address(uint160(id)) == address(this);
@@ -60,14 +67,6 @@ library Ids {
60
67
  return id;
61
68
  }
62
69
 
63
- /// @notice Assert that `id` is a command ID and return its embedded ABI selector.
64
- /// @param id Node ID to validate.
65
- /// @return selector 4-byte command selector stored in bits [191:160].
66
- function commandSelector(uint id) internal pure returns (bytes4 selector) {
67
- if (!isCommand(id)) revert InvalidId();
68
- return bytes4(uint32(id >> 160));
69
- }
70
-
71
70
  /// @notice Assert that `id` is a peer ID and return it unchanged.
72
71
  /// @param id Node ID to validate.
73
72
  /// @return pid The same `id` value if it is a peer.
@@ -76,14 +75,6 @@ library Ids {
76
75
  return id;
77
76
  }
78
77
 
79
- /// @notice Assert that `id` is a peer ID and return its embedded ABI selector.
80
- /// @param id Node ID to validate.
81
- /// @return selector 4-byte peer selector stored in bits [191:160].
82
- function peerSelector(uint id) internal pure returns (bytes4 selector) {
83
- if (!isPeer(id)) revert InvalidId();
84
- return bytes4(uint32(id >> 160));
85
- }
86
-
87
78
  /// @notice Assert that `id` is a query ID and return it unchanged.
88
79
  /// @param id Node ID to validate.
89
80
  /// @return queryId The same `id` value if it is a query.
@@ -92,12 +83,40 @@ library Ids {
92
83
  return id;
93
84
  }
94
85
 
86
+ /// @notice Assert that `id` is a guard action ID and return it unchanged.
87
+ /// @param id Node ID to validate.
88
+ /// @return guardId The same `id` value if it is a guard action.
89
+ function guard(uint id) internal pure returns (uint guardId) {
90
+ if (!isGuard(id)) revert InvalidId();
91
+ return id;
92
+ }
93
+
94
+ /// @notice Assert that `id` is a command ID and return its embedded ABI selector.
95
+ /// @param id Node ID to validate.
96
+ /// @return selector 4-byte command selector stored in bits [191:160].
97
+ function commandSelector(uint id) internal pure returns (bytes4 selector) {
98
+ return bytes4(uint32(command(id) >> 160));
99
+ }
100
+
101
+ /// @notice Assert that `id` is a peer ID and return its embedded ABI selector.
102
+ /// @param id Node ID to validate.
103
+ /// @return selector 4-byte peer selector stored in bits [191:160].
104
+ function peerSelector(uint id) internal pure returns (bytes4 selector) {
105
+ return bytes4(uint32(peer(id) >> 160));
106
+ }
107
+
95
108
  /// @notice Assert that `id` is a query ID and return its embedded ABI selector.
96
109
  /// @param id Node ID to validate.
97
110
  /// @return selector 4-byte query selector stored in bits [191:160].
98
111
  function querySelector(uint id) internal pure returns (bytes4 selector) {
99
- if (!isQuery(id)) revert InvalidId();
100
- return bytes4(uint32(id >> 160));
112
+ return bytes4(uint32(query(id) >> 160));
113
+ }
114
+
115
+ /// @notice Assert that `id` is a guard action ID and return its embedded ABI selector.
116
+ /// @param id Node ID to validate.
117
+ /// @return selector 4-byte guard selector stored in bits [191:160].
118
+ function guardSelector(uint id) internal pure returns (bytes4 selector) {
119
+ return bytes4(uint32(guard(id) >> 160));
101
120
  }
102
121
 
103
122
  /// @notice Assert that `id` is the host ID of `addr` on the current chain.
@@ -146,6 +165,16 @@ library Ids {
146
165
  return id;
147
166
  }
148
167
 
168
+ /// @notice Build a chain-local guard action ID for the given selector and contract.
169
+ /// @param selector 4-byte ABI selector of the guard action entry point.
170
+ /// @param target Guard action contract address.
171
+ /// @return Guard action node ID embedding both the selector and address.
172
+ function toGuard(bytes4 selector, address target) internal view returns (uint) {
173
+ uint id = toLocalBase(Guard) | uint(uint160(target));
174
+ id |= uint(uint32(selector)) << 160;
175
+ return id;
176
+ }
177
+
149
178
  /// @notice Extract the contract address from any local node ID.
150
179
  /// Reverts if `id` does not belong to the local node family.
151
180
  /// @param id Node ID (host, command, or peer).
@@ -174,6 +203,8 @@ library Selectors {
174
203
  string constant PeerArgs = "(bytes)";
175
204
  /// @dev ABI argument encoding for query entry points: `(bytes)`.
176
205
  string constant QueryArgs = "(bytes)";
206
+ /// @dev ABI argument encoding for guard action entry points: `(bytes)`.
207
+ string constant GuardArgs = "(bytes)";
177
208
 
178
209
  /// @notice Derive the 4-byte ABI selector for a named command.
179
210
  /// The selector is `keccak256(name ++ CommandArgs)[0:4]`.
@@ -198,4 +229,12 @@ library Selectors {
198
229
  function query(string memory name) internal pure returns (bytes4) {
199
230
  return bytes4(keccak256(bytes.concat(bytes(name), bytes(QueryArgs))));
200
231
  }
232
+
233
+ /// @notice Derive the 4-byte ABI selector for a named guard action.
234
+ /// The selector is `keccak256(name ++ GuardArgs)[0:4]`.
235
+ /// @param name Guard action function name (without arguments).
236
+ /// @return 4-byte selector.
237
+ function guard(string memory name) internal pure returns (bytes4) {
238
+ return bytes4(keccak256(bytes.concat(bytes(name), bytes(GuardArgs))));
239
+ }
201
240
  }
package/utils/Layout.sol CHANGED
@@ -37,10 +37,12 @@ library Layout {
37
37
 
38
38
  /// @dev Admin account — chain-local, backed by an EVM address.
39
39
  uint8 constant Admin = 0x01;
40
+ /// @dev Guardian account — chain-local, backed by an EVM address.
41
+ uint8 constant Guardian = 0x02;
40
42
  /// @dev User account — chain-agnostic, backed by an EVM address.
41
- uint8 constant User = 0x02;
43
+ uint8 constant User = 0x03;
42
44
  /// @dev Keccak account — opaque 28-byte keccak commitment.
43
- uint8 constant Keccak = 0x03;
45
+ uint8 constant Keccak = 0x04;
44
46
 
45
47
  // -------------------------------------------------------------------------
46
48
  // Node subtype tags (uint8, fourth byte of the ID type field)
@@ -54,6 +56,8 @@ library Layout {
54
56
  uint8 constant Peer = 0x03;
55
57
  /// @dev Node is a query contract.
56
58
  uint8 constant Query = 0x04;
59
+ /// @dev Node is a guardian-only direct action.
60
+ uint8 constant Guard = 0x05;
57
61
 
58
62
  // -------------------------------------------------------------------------
59
63
  // Asset subtype tags (uint8, fourth byte of the ID type field)
package/Queries.sol DELETED
@@ -1,10 +0,0 @@
1
- // SPDX-License-Identifier: GPL-3.0-only
2
- pragma solidity ^0.8.33;
3
-
4
- // Aggregator: re-exports query abstractions and reusable query bases.
5
- // Import this file to build rootzero query contracts without managing individual paths.
6
-
7
- import { AssetStatus, AssetStatusHook } from "./queries/Assets.sol";
8
- import { GetPosition, GetPositionHook } from "./queries/Positions.sol";
9
- import { GetBalances, GetBalancesHook } from "./queries/Balances.sol";
10
- import { QueryBase, encodeQueryCall } from "./queries/Base.sol";
package/events/Access.sol DELETED
@@ -1,21 +0,0 @@
1
- // SPDX-License-Identifier: GPL-3.0-only
2
- pragma solidity ^0.8.33;
3
-
4
- import { EventEmitter } from "./Emitter.sol";
5
-
6
- /// @notice Emitted when a node's authorization status changes on a host.
7
- abstract contract AccessEvent is EventEmitter {
8
- string private constant ABI = "event Access(uint indexed host, uint node, bool trusted)";
9
-
10
- /// @param host Host node ID where the authorization change occurred.
11
- /// @param node Node ID that was authorized or deauthorized.
12
- /// @param trusted True if the node was authorized, false if deauthorized.
13
- event Access(uint indexed host, uint node, bool trusted);
14
-
15
- constructor() {
16
- emit EventAbi(ABI);
17
- }
18
- }
19
-
20
-
21
-