@rootzero/contracts 1.1.0 → 1.3.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 (57) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/Endpoints.sol +10 -7
  3. package/Events.sol +1 -0
  4. package/README.md +300 -79
  5. package/blocks/Cursors.sol +49 -14
  6. package/blocks/Keys.sol +4 -0
  7. package/blocks/Schema.sol +10 -4
  8. package/blocks/Writers.sol +16 -0
  9. package/commands/Base.sol +10 -9
  10. package/commands/Burn.sol +4 -5
  11. package/commands/Credit.sol +4 -5
  12. package/commands/Debit.sol +4 -5
  13. package/commands/Deposit.sol +8 -10
  14. package/commands/Payout.sol +5 -6
  15. package/commands/Provision.sol +8 -10
  16. package/commands/Relay.sol +4 -5
  17. package/commands/Withdraw.sol +4 -5
  18. package/commands/admin/AllowAssets.sol +4 -5
  19. package/commands/admin/Allowance.sol +4 -4
  20. package/commands/admin/Appoint.sol +4 -5
  21. package/commands/admin/Authorize.sol +4 -5
  22. package/commands/admin/DenyAssets.sol +4 -5
  23. package/commands/admin/Destroy.sol +3 -4
  24. package/commands/admin/Dismiss.sol +4 -5
  25. package/commands/admin/Execute.sol +4 -5
  26. package/commands/admin/Init.sol +3 -4
  27. package/commands/admin/Label.sol +35 -0
  28. package/commands/admin/Unauthorize.sol +4 -5
  29. package/core/Host.sol +7 -11
  30. package/core/Pipeline.sol +1 -1
  31. package/docs/Schema.md +59 -2
  32. package/events/Admin.sol +3 -9
  33. package/events/Chain.sol +2 -3
  34. package/events/Command.sol +3 -9
  35. package/events/Guard.sol +2 -3
  36. package/events/Introduction.sol +2 -4
  37. package/events/Labeled.sol +21 -0
  38. package/events/Peer.sol +2 -11
  39. package/events/Query.sol +2 -3
  40. package/events/Transfer.sol +1 -1
  41. package/guards/Base.sol +7 -6
  42. package/guards/Revoke.sol +4 -5
  43. package/package.json +1 -1
  44. package/peer/AllowAssets.sol +9 -5
  45. package/peer/Allowance.sol +9 -5
  46. package/peer/BalancePull.sol +9 -5
  47. package/peer/Base.sol +7 -6
  48. package/peer/Credit.sol +39 -0
  49. package/peer/Debit.sol +39 -0
  50. package/peer/DenyAssets.sol +9 -5
  51. package/peer/Dispatch.sol +9 -5
  52. package/peer/Pipe.sol +9 -5
  53. package/peer/Settle.sol +9 -5
  54. package/queries/Assets.sol +4 -4
  55. package/queries/Balances.sol +4 -4
  56. package/queries/Base.sol +9 -8
  57. package/queries/Positions.sol +4 -5
@@ -11,12 +11,11 @@ using Cursors for Cur;
11
11
  /// Each NODE block in the request is deauthorized on the host.
12
12
  /// Only callable by the admin account.
13
13
  abstract contract Unauthorize is CommandBase, AdminEvent {
14
- string private constant NAME = "unauthorize";
15
-
16
- uint internal immutable unauthorizeId = commandId(NAME);
14
+ uint internal immutable unauthorizeId = commandId(this.unauthorize.selector);
17
15
 
18
16
  constructor() {
19
- emit Admin(host, unauthorizeId, NAME, "1:0:0", Schemas.Node, Keys.Empty, Keys.Empty, false, false);
17
+ emit Admin(host, unauthorizeId, "1:0:0", Schemas.Node, Keys.Empty, Keys.Empty, false);
18
+ emit Labeled(unauthorizeId, bytes32(0), "unauthorize");
20
19
  }
21
20
 
22
21
  /// @notice Unauthorize each NODE block in the admin request.
@@ -25,7 +24,7 @@ abstract contract Unauthorize is CommandBase, AdminEvent {
25
24
  function unauthorize(
26
25
  CommandContext calldata c
27
26
  ) external onlyAdmin(c.account) returns (bytes memory) {
28
- (Cur memory request, , ) = Cursors.init(c.request, 0, 1);
27
+ (Cur memory request, , ) = Cursors.init(c.request, 1);
29
28
 
30
29
  while (request.i < request.len) {
31
30
  uint node = request.unpackNode();
package/core/Host.sol CHANGED
@@ -7,6 +7,7 @@ import {Authorize} from "../commands/admin/Authorize.sol";
7
7
  import {Dismiss} from "../commands/admin/Dismiss.sol";
8
8
  import {Unauthorize} from "../commands/admin/Unauthorize.sol";
9
9
  import {ExecutePayable} from "../commands/admin/Execute.sol";
10
+ import {Label} from "../commands/admin/Label.sol";
10
11
  import {Revoke} from "../guards/Revoke.sol";
11
12
  import {IntroductionEvent} from "../events/Introduction.sol";
12
13
  import {Ids} from "../utils/Ids.sol";
@@ -17,9 +18,7 @@ interface IHostIntroduction {
17
18
  /// @notice Record a host introduction claim.
18
19
  /// @param peer Host node ID being introduced.
19
20
  /// @param blocknum Block number at which the introduction was made.
20
- /// @param version Protocol version the host is running.
21
- /// @param namespace Human-readable namespace or label for the host.
22
- function introduce(uint peer, uint blocknum, uint16 version, string calldata namespace) external;
21
+ function introduce(uint peer, uint blocknum) external;
23
22
  }
24
23
 
25
24
  /// @title Host
@@ -34,6 +33,7 @@ abstract contract Host is
34
33
  Revoke,
35
34
  Appoint,
36
35
  Dismiss,
36
+ Label,
37
37
  ExecutePayable,
38
38
  IntroductionEvent,
39
39
  IHostIntroduction
@@ -41,21 +41,17 @@ abstract contract Host is
41
41
  /// @param cmdr Commander address; passed to `AccessControl`.
42
42
  /// If `cmdr` is a deployed contract, the host calls `introduce`
43
43
  /// on it during construction.
44
- /// @param version Protocol version number to publish in the announcement.
45
- /// @param namespace Human-readable namespace string for the host.
46
- constructor(address cmdr, uint16 version, string memory namespace) AccessControl(cmdr) {
44
+ constructor(address cmdr) AccessControl(cmdr) {
47
45
  if (cmdr == address(0) || cmdr == address(this) || cmdr.code.length == 0) return;
48
- IHostIntroduction(cmdr).introduce(host, block.number, version, namespace);
46
+ IHostIntroduction(cmdr).introduce(host, block.number);
49
47
  }
50
48
 
51
49
  /// @notice Record a host introduction claim.
52
50
  /// @dev Validates that `peer` matches `msg.sender`; it does not authorize or trust the introduced host.
53
51
  /// @param peer Host node ID being introduced.
54
52
  /// @param blocknum Block number at which the host was deployed.
55
- /// @param version Protocol version the host implements.
56
- /// @param namespace Human-readable namespace string for the host.
57
- function introduce(uint peer, uint blocknum, uint16 version, string calldata namespace) external {
58
- emit Introduction(Ids.matchHost(peer, msg.sender), blocknum, version, namespace);
53
+ function introduce(uint peer, uint blocknum) external {
54
+ emit Introduction(Ids.matchHost(peer, msg.sender), blocknum);
59
55
  }
60
56
 
61
57
  /// @notice Accept native ETH transfers (e.g. from command value flows).
package/core/Pipeline.sol CHANGED
@@ -42,7 +42,7 @@ abstract contract Pipeline is Payable {
42
42
  bytes calldata steps,
43
43
  Budget memory budget
44
44
  ) internal {
45
- (Cur memory input, , ) = Cursors.init(steps, 0, 1);
45
+ (Cur memory input, , ) = Cursors.init(steps, 1);
46
46
 
47
47
  while (input.i < input.len) {
48
48
  (uint target, uint resources, bytes calldata request) = input.unpackStep();
package/docs/Schema.md CHANGED
@@ -101,6 +101,11 @@ the whole batch and are not counted as per-operation prime blocks.
101
101
  The prime item cannot be optional. If a command needs a per-operation marker with
102
102
  no payload, use a zero-payload block such as `#unit`.
103
103
 
104
+ Command request and state streams currently use a narrower convention than the
105
+ full block grammar: each is a single run of blocks, without additional global
106
+ items. Future protocol surfaces may use the more flexible top-level structure,
107
+ but command discovery metadata should describe only that one-run shape.
108
+
104
109
  ## Aliases
105
110
 
106
111
  Aliases are presentation metadata for tooling. They do not change payload layout
@@ -113,6 +118,53 @@ maybe #account { bytes32 account } as recipient
113
118
 
114
119
  Aliases may be used on any block item, including child blocks and prime items.
115
120
 
121
+ ## Field Paths
122
+
123
+ Field names and aliases may use dotted paths for offchain projection. A dotted
124
+ path does not change the block key, payload bytes, payload length, cursor
125
+ behavior, or any onchain validation. It is metadata only.
126
+
127
+ ```txt
128
+ #dispatch { uint dst.chain, uint dst.resources, #bytes as dst.payload }
129
+ ```
130
+
131
+ This has the same runtime layout as:
132
+
133
+ ```txt
134
+ #dispatch { uint chain, uint resources, #bytes as payload }
135
+ ```
136
+
137
+ Offchain tooling may decode the dotted form into a nested object:
138
+
139
+ ```ts
140
+ {
141
+ dst: {
142
+ chain,
143
+ resources,
144
+ payload
145
+ }
146
+ }
147
+ ```
148
+
149
+ Encoding and decoding must still follow schema declaration order, not object
150
+ property order. Fields with the same path prefix do not need to be contiguous,
151
+ although contiguous fields are easier to read when they represent one logical
152
+ object.
153
+
154
+ Tooling should reject duplicate full paths and prefix/value collisions:
155
+
156
+ ```txt
157
+ uint dst.chain, uint dst.chain // duplicate path
158
+ uint dst, uint dst.chain // prefix/value collision
159
+ ```
160
+
161
+ The same rule applies to block aliases:
162
+
163
+ ```txt
164
+ #call { uint target, uint resources, #bytes as calldata.payload }
165
+ maybe #account { bytes32 account } as recipient.account
166
+ ```
167
+
116
168
  ## Field Types
117
169
 
118
170
  Supported field types are chain-neutral:
@@ -140,10 +192,12 @@ endowment in wei; higher bits are reserved for execution resources such as gas.
140
192
 
141
193
  ## Identifiers
142
194
 
143
- Block names, field names, and aliases use lower camelCase ASCII identifiers:
195
+ Block names use lower camelCase ASCII identifiers. Field names and aliases use
196
+ one or more lower camelCase path segments separated by dots:
144
197
 
145
198
  ```txt
146
199
  [a-z][a-zA-Z0-9]*
200
+ [a-z][a-zA-Z0-9]*(\.[a-z][a-zA-Z0-9]*)*
147
201
  ```
148
202
 
149
203
  Invalid examples:
@@ -153,10 +207,13 @@ Amount
153
207
  asset_meta
154
208
  asset-meta
155
209
  0account
210
+ asset.
211
+ .asset
156
212
  ```
157
213
 
158
214
  Reserved words include `maybe`, `many`, `as`, all field type names, and the
159
- reserved block names `bytes`, `data`, and `list`.
215
+ reserved block names `bytes`, `data`, and `list`. For dotted paths, reserved
216
+ words are invalid in any path segment.
160
217
 
161
218
  ## Reserved Blocks
162
219
 
package/events/Admin.sol CHANGED
@@ -1,34 +1,28 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import { EventEmitter } from "./Emitter.sol";
4
+ import {EventEmitter} from "./Emitter.sol";
5
5
 
6
6
  /// @notice Emitted once per admin command during host deployment to publish its request schema and state keys.
7
7
  abstract contract AdminEvent is EventEmitter {
8
8
  string private constant ABI =
9
- "event Admin(uint indexed host, uint id, string name, bytes32 shape, string request, bytes4 state, bytes4 output, bool postcheck, bool funded)";
9
+ "event Admin(uint indexed host, uint id, bytes32 shape, string request, bytes4 state, bytes4 output, bool funded)";
10
10
 
11
11
  /// @param host Host node ID that owns this admin command.
12
12
  /// @param id Command node ID.
13
- /// @param name Human-readable command name.
14
13
  /// @param shape Per-operation block counts encoded as `request:state:output`.
15
- /// The request count covers only the input request run. If `postcheck` is true,
16
- /// a constraint run follows the input run, or starts the request when `request`
17
- /// is empty. State globals may follow the state run and are excluded.
14
+ /// Request and state are each a single run of blocks under the current command convention.
18
15
  /// @param request Schema DSL string describing the input request run, or empty if none.
19
16
  /// @param state Block key expected for input state, `Keys.Empty`, or `Keys.Any`.
20
17
  /// @param output Block key produced for output state, or `Keys.Empty`.
21
- /// @param postcheck Whether command output is validated after execution.
22
18
  /// @param funded Whether the command entrypoint accepts nonzero `msg.value`.
23
19
  event Admin(
24
20
  uint indexed host,
25
21
  uint id,
26
- string name,
27
22
  bytes32 shape,
28
23
  string request,
29
24
  bytes4 state,
30
25
  bytes4 output,
31
- bool postcheck,
32
26
  bool funded
33
27
  );
34
28
 
package/events/Chain.sol CHANGED
@@ -5,14 +5,13 @@ import {EventEmitter} from "./Emitter.sol";
5
5
 
6
6
  /// @notice Emitted when a chain/domain node is announced.
7
7
  abstract contract ChainEvent is EventEmitter {
8
- string private constant ABI = "event Chain(uint indexed chain, bytes32 native, uint commander, bytes32 admin, string name)";
8
+ string private constant ABI = "event Chain(uint indexed chain, bytes32 native, uint commander, bytes32 admin)";
9
9
 
10
10
  /// @param chain Chain node ID.
11
11
  /// @param native Native asset ID for the chain.
12
12
  /// @param commander Commander host node ID for the chain.
13
13
  /// @param admin Admin account for the commander host on the chain.
14
- /// @param name Chain or domain name.
15
- event Chain(uint indexed chain, bytes32 native, uint commander, bytes32 admin, string name);
14
+ event Chain(uint indexed chain, bytes32 native, uint commander, bytes32 admin);
16
15
 
17
16
  constructor() {
18
17
  emit EventAbi(ABI);
@@ -1,34 +1,28 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import { EventEmitter } from "./Emitter.sol";
4
+ import {EventEmitter} from "./Emitter.sol";
5
5
 
6
6
  /// @notice Emitted once per command during host deployment to publish its request schema and state keys.
7
7
  abstract contract CommandEvent is EventEmitter {
8
8
  string private constant ABI =
9
- "event Command(uint indexed host, uint id, string name, bytes32 shape, string request, bytes4 state, bytes4 output, bool postcheck, bool funded)";
9
+ "event Command(uint indexed host, uint id, bytes32 shape, string request, bytes4 state, bytes4 output, bool funded)";
10
10
 
11
11
  /// @param host Host node ID that owns this command.
12
12
  /// @param id Command node ID.
13
- /// @param name Human-readable command name.
14
13
  /// @param shape Per-operation block counts encoded as `request:state:output`.
15
- /// The request count covers only the input request run. If `postcheck` is true,
16
- /// a constraint run follows the input run, or starts the request when `request`
17
- /// is empty. State globals may follow the state run and are excluded.
14
+ /// Request and state are each a single run of blocks under the current command convention.
18
15
  /// @param request Schema string describing the input request run, or empty if none.
19
16
  /// @param state Block key expected for input state, `Keys.Empty`, or `Keys.Any`.
20
17
  /// @param output Block key produced for output state, or `Keys.Empty`.
21
- /// @param postcheck Whether command output is validated after execution.
22
18
  /// @param funded Whether the command entrypoint accepts nonzero `msg.value`.
23
19
  event Command(
24
20
  uint indexed host,
25
21
  uint id,
26
- string name,
27
22
  bytes32 shape,
28
23
  string request,
29
24
  bytes4 state,
30
25
  bytes4 output,
31
- bool postcheck,
32
26
  bool funded
33
27
  );
34
28
 
package/events/Guard.sol CHANGED
@@ -5,13 +5,12 @@ import {EventEmitter} from "./Emitter.sol";
5
5
 
6
6
  /// @notice Emitted once per guard action during host deployment to publish its request schema.
7
7
  abstract contract GuardEvent is EventEmitter {
8
- string private constant ABI = "event Guard(uint indexed host, uint id, string name, string request)";
8
+ string private constant ABI = "event Guard(uint indexed host, uint id, string request)";
9
9
 
10
10
  /// @param host Host node ID that owns this guard action.
11
11
  /// @param id Guard action node ID.
12
- /// @param name Human-readable guard action name.
13
12
  /// @param request Schema DSL string describing the guard action request shape.
14
- event Guard(uint indexed host, uint id, string name, string request);
13
+ event Guard(uint indexed host, uint id, string request);
15
14
 
16
15
  constructor() {
17
16
  emit EventAbi(ABI);
@@ -5,13 +5,11 @@ import { EventEmitter } from "./Emitter.sol";
5
5
 
6
6
  /// @notice Emitted when a host introduces itself to another host.
7
7
  abstract contract IntroductionEvent is EventEmitter {
8
- string private constant ABI = "event Introduction(uint indexed host, uint blocknum, uint16 version, string namespace)";
8
+ string private constant ABI = "event Introduction(uint indexed host, uint blocknum)";
9
9
 
10
10
  /// @param host Host node ID of the introducing contract.
11
11
  /// @param blocknum Block number at which the host was deployed.
12
- /// @param version Protocol version the host implements.
13
- /// @param namespace Human-readable namespace string for the host.
14
- event Introduction(uint indexed host, uint blocknum, uint16 version, string namespace);
12
+ event Introduction(uint indexed host, uint blocknum);
15
13
 
16
14
  constructor() {
17
15
  emit EventAbi(ABI);
@@ -0,0 +1,21 @@
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 to attach a human-readable namespaced label to a node ID.
7
+ /// @dev Labels are claims by the emitting contract. Any contract may emit a
8
+ /// label for any ID, so off-chain indexers must decide which emitters are
9
+ /// trusted for each labeled ID or namespace.
10
+ abstract contract LabeledEvent is EventEmitter {
11
+ string private constant ABI = "event Labeled(uint indexed id, bytes32 namespace, string name)";
12
+
13
+ /// @param id Node or capability ID being labeled.
14
+ /// @param namespace Label namespace.
15
+ /// @param name Human-readable name within the namespace.
16
+ event Labeled(uint indexed id, bytes32 namespace, string name);
17
+
18
+ constructor() {
19
+ emit EventAbi(ABI);
20
+ }
21
+ }
package/events/Peer.sol CHANGED
@@ -6,24 +6,15 @@ import {EventEmitter} from "./Emitter.sol";
6
6
  /// @notice Emitted once per peer during host deployment to publish its request and response schemas.
7
7
  abstract contract PeerEvent is EventEmitter {
8
8
  string private constant ABI =
9
- "event Peer(uint indexed host, uint id, string name, bytes32 shape, string request, string response, bool funded)";
9
+ "event Peer(uint indexed host, uint id, bytes32 shape, string request, string response, bool funded)";
10
10
 
11
11
  /// @param host Host node ID that owns this peer.
12
12
  /// @param id Peer node ID.
13
- /// @param name Human-readable peer name.
14
13
  /// @param shape Per-operation block counts encoded as `request:response`.
15
14
  /// @param request Schema DSL string describing the input request run, or empty if none.
16
15
  /// @param response Schema DSL string describing the peer response shape.
17
16
  /// @param funded Whether the peer entrypoint accepts nonzero `msg.value`.
18
- event Peer(
19
- uint indexed host,
20
- uint id,
21
- string name,
22
- bytes32 shape,
23
- string request,
24
- string response,
25
- bool funded
26
- );
17
+ event Peer(uint indexed host, uint id, bytes32 shape, string request, string response, bool funded);
27
18
 
28
19
  constructor() {
29
20
  emit EventAbi(ABI);
package/events/Query.sol CHANGED
@@ -5,15 +5,14 @@ import {EventEmitter} from "./Emitter.sol";
5
5
 
6
6
  /// @notice Emitted once per query during host deployment to publish its request and response schemas.
7
7
  abstract contract QueryEvent is EventEmitter {
8
- string private constant ABI = "event Query(uint indexed host, uint id, string name, bytes32 shape, string request, string response)";
8
+ string private constant ABI = "event Query(uint indexed host, uint id, bytes32 shape, string request, string response)";
9
9
 
10
10
  /// @param host Host node ID that owns this query.
11
11
  /// @param id Query node ID.
12
- /// @param name Human-readable query name.
13
12
  /// @param shape Per-operation block counts encoded as `request:response`.
14
13
  /// @param request Schema DSL string describing the input request run, or empty if none.
15
14
  /// @param response Schema DSL string describing the query response shape.
16
- event Query(uint indexed host, uint id, string name, bytes32 shape, string request, string response);
15
+ event Query(uint indexed host, uint id, bytes32 shape, string request, string response);
17
16
 
18
17
  constructor() {
19
18
  emit EventAbi(ABI);
@@ -5,7 +5,7 @@ import { EventEmitter } from "./Emitter.sol";
5
5
 
6
6
  /// @notice Emitted when an asset moves from one account to another.
7
7
  abstract contract TransferEvent is EventEmitter {
8
- string private constant ABI = "event Transfer(bytes32 indexed from, bytes32 to, bytes32 asset, bytes32 meta, uint amount, uint32 action, uint context)";
8
+ string private constant ABI = "event Transfer(bytes32 indexed account, bytes32 to, bytes32 asset, bytes32 meta, uint amount, uint32 action, uint context)";
9
9
 
10
10
  /// @param account Source account identifier.
11
11
  /// @param to Destination account identifier.
package/guards/Base.sol CHANGED
@@ -3,7 +3,8 @@ pragma solidity ^0.8.33;
3
3
 
4
4
  import {AccessControl} from "../core/Access.sol";
5
5
  import {GuardEvent} from "../events/Guard.sol";
6
- import {Ids, Selectors} from "../utils/Ids.sol";
6
+ import {LabeledEvent} from "../events/Labeled.sol";
7
+ import {Ids} from "../utils/Ids.sol";
7
8
 
8
9
  /// @notice ABI-encode a guard action call from a target guard ID and request block stream.
9
10
  /// @dev Derives the function selector from `target` via `Ids.guardSelector(target)`.
@@ -19,17 +20,17 @@ function encodeGuardCall(uint target, bytes calldata request) pure returns (byte
19
20
  /// @title GuardBase
20
21
  /// @notice Abstract base for guardian-only direct host actions.
21
22
  /// Guard actions are non-payable direct calls with no command context, state, or response.
22
- abstract contract GuardBase is AccessControl, GuardEvent {
23
+ abstract contract GuardBase is AccessControl, GuardEvent, LabeledEvent {
23
24
  /// @dev Restrict execution to active guardian addresses.
24
25
  modifier onlyGuardian() {
25
26
  if (!isGuardian(msg.sender)) revert AccessDenied();
26
27
  _;
27
28
  }
28
29
 
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).
30
+ /// @notice Derive the deterministic node ID for a guard action selector on this contract.
31
+ /// @param selector Guard action entrypoint selector.
31
32
  /// @return Guard action node ID.
32
- function guardId(string memory name) internal view returns (uint) {
33
- return Ids.toGuard(Selectors.guard(name), address(this));
33
+ function guardId(bytes4 selector) internal view returns (uint) {
34
+ return Ids.toGuard(selector, address(this));
34
35
  }
35
36
  }
package/guards/Revoke.sol CHANGED
@@ -10,16 +10,15 @@ using Cursors for Cur;
10
10
  /// Each NODE block in the request is deauthorized on the host.
11
11
  /// Only callable by active guardian addresses.
12
12
  abstract contract Revoke is GuardBase {
13
- string private constant NAME = "revoke";
14
-
15
- uint internal immutable revokeId = guardId(NAME);
13
+ uint internal immutable revokeId = guardId(this.revoke.selector);
16
14
 
17
15
  constructor() {
18
- emit Guard(host, revokeId, NAME, Schemas.Node);
16
+ emit Guard(host, revokeId, Schemas.Node);
17
+ emit Labeled(revokeId, bytes32(0), "revoke");
19
18
  }
20
19
 
21
20
  function revoke(bytes calldata request) external onlyGuardian {
22
- (Cur memory input, , ) = Cursors.init(request, 0, 1);
21
+ (Cur memory input, , ) = Cursors.init(request, 1);
23
22
 
24
23
  while (input.i < input.len) {
25
24
  uint node = input.unpackNode();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rootzero/contracts",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "Solidity contracts and protocol building blocks for rootzero hosts and commands.",
5
5
  "private": false,
6
6
  "license": "GPL-3.0-only",
@@ -7,22 +7,26 @@ import { Cursors, Cur, Schemas } from "../Cursors.sol";
7
7
 
8
8
  using Cursors for Cur;
9
9
 
10
+ interface IPeerAllowAssets {
11
+ function peerAllowAssets(bytes calldata request) external returns (bytes memory);
12
+ }
13
+
10
14
  /// @title PeerAllowAssets
11
15
  /// @notice Peer that permits a list of (asset, meta) pairs on behalf of a peer host.
12
16
  /// Each ASSET block in the request calls `allowAsset`. Restricted to trusted peers.
13
- abstract contract PeerAllowAssets is PeerBase, AllowAssetsHook {
14
- string private constant NAME = "peerAllowAssets";
15
- uint internal immutable peerAllowAssetsId = peerId(NAME);
17
+ abstract contract PeerAllowAssets is PeerBase, AllowAssetsHook, IPeerAllowAssets {
18
+ uint internal immutable peerAllowAssetsId = peerId(this.peerAllowAssets.selector);
16
19
 
17
20
  constructor() {
18
- emit Peer(host, peerAllowAssetsId, NAME, "1:0", Schemas.Asset, "", false);
21
+ emit Peer(host, peerAllowAssetsId, "1:0", Schemas.Asset, "", false);
22
+ emit Labeled(peerAllowAssetsId, bytes32(0), "peerAllowAssets");
19
23
  }
20
24
 
21
25
  /// @notice Execute the allow-assets peer call.
22
26
  /// @param request ASSET block stream supplied by the trusted peer.
23
27
  /// @return Empty response bytes.
24
28
  function peerAllowAssets(bytes calldata request) external onlyPeer returns (bytes memory) {
25
- (Cur memory assets, , ) = Cursors.init(request, 0, 1);
29
+ (Cur memory assets, , ) = Cursors.init(request, 1);
26
30
 
27
31
  while (assets.i < assets.len) {
28
32
  (bytes32 asset, bytes32 meta) = assets.unpackAsset();
@@ -7,23 +7,27 @@ import {Cursors, Cur, Schemas} from "../Cursors.sol";
7
7
 
8
8
  using Cursors for Cur;
9
9
 
10
+ interface IPeerAllowance {
11
+ function peerAllowance(bytes calldata request) external returns (bytes memory);
12
+ }
13
+
10
14
  /// @title PeerAllowance
11
15
  /// @notice Peer that lets a trusted peer host request or refresh its own allowance.
12
16
  /// Each AMOUNT block in the request is scoped to the peer host and passed to the
13
17
  /// shared allowance hook as a host-scoped allowance. Restricted to trusted peers.
14
- abstract contract PeerAllowance is PeerBase, AllowanceHook {
15
- string private constant NAME = "peerAllowance";
16
- uint internal immutable peerAllowanceId = peerId(NAME);
18
+ abstract contract PeerAllowance is PeerBase, AllowanceHook, IPeerAllowance {
19
+ uint internal immutable peerAllowanceId = peerId(this.peerAllowance.selector);
17
20
 
18
21
  constructor() {
19
- emit Peer(host, peerAllowanceId, NAME, "1:0", Schemas.Amount, "", false);
22
+ emit Peer(host, peerAllowanceId, "1:0", Schemas.Amount, "", false);
23
+ emit Labeled(peerAllowanceId, bytes32(0), "peerAllowance");
20
24
  }
21
25
 
22
26
  /// @notice Execute the allowance peer call.
23
27
  /// @param request AMOUNT block stream requested by the trusted peer.
24
28
  /// @return Empty response bytes.
25
29
  function peerAllowance(bytes calldata request) external onlyPeer returns (bytes memory) {
26
- (Cur memory amounts, , ) = Cursors.init(request, 0, 1);
30
+ (Cur memory amounts, , ) = Cursors.init(request, 1);
27
31
  uint peer = caller();
28
32
 
29
33
  while (amounts.i < amounts.len) {
@@ -6,6 +6,10 @@ import {Cursors, Cur, Schemas} from "../Cursors.sol";
6
6
 
7
7
  using Cursors for Cur;
8
8
 
9
+ interface IPeerBalancePull {
10
+ function peerBalancePull(bytes calldata request) external returns (bytes memory);
11
+ }
12
+
9
13
  abstract contract BalancePullHook {
10
14
  /// @notice Override to process one incoming balance-based pull request from a peer host.
11
15
  /// @param peer Peer host node ID for this request.
@@ -19,19 +23,19 @@ abstract contract BalancePullHook {
19
23
  /// @notice Peer that pulls requested balances from a peer host into this one.
20
24
  /// Each BALANCE block in the request calls `balancePull(peer, asset, meta, amount)`.
21
25
  /// Restricted to trusted peers.
22
- abstract contract PeerBalancePull is PeerBase, BalancePullHook {
23
- string private constant NAME = "peerBalancePull";
24
- uint internal immutable peerBalancePullId = peerId(NAME);
26
+ abstract contract PeerBalancePull is PeerBase, BalancePullHook, IPeerBalancePull {
27
+ uint internal immutable peerBalancePullId = peerId(this.peerBalancePull.selector);
25
28
 
26
29
  constructor() {
27
- emit Peer(host, peerBalancePullId, NAME, "1:0", Schemas.Balance, "", false);
30
+ emit Peer(host, peerBalancePullId, "1:0", Schemas.Balance, "", false);
31
+ emit Labeled(peerBalancePullId, bytes32(0), "peerBalancePull");
28
32
  }
29
33
 
30
34
  /// @notice Execute the balance-pull peer call.
31
35
  /// @param request BALANCE block stream requested by the trusted peer.
32
36
  /// @return Empty response bytes.
33
37
  function peerBalancePull(bytes calldata request) external onlyPeer returns (bytes memory) {
34
- (Cur memory input, , ) = Cursors.init(request, 0, 1);
38
+ (Cur memory input, , ) = Cursors.init(request, 1);
35
39
  uint peer = caller();
36
40
 
37
41
  while (input.i < input.len) {
package/peer/Base.sol CHANGED
@@ -3,7 +3,8 @@ pragma solidity ^0.8.33;
3
3
 
4
4
  import { NodeCalls } from "../core/Calls.sol";
5
5
  import { PeerEvent } from "../events/Peer.sol";
6
- import { Ids, Selectors } from "../utils/Ids.sol";
6
+ import { LabeledEvent } from "../events/Labeled.sol";
7
+ import { Ids } from "../utils/Ids.sol";
7
8
 
8
9
  /// @notice ABI-encode a peer call from a target peer ID and request block stream.
9
10
  /// @dev Derives the function selector from `target` via `Ids.peerSelector(target)`.
@@ -20,7 +21,7 @@ function encodePeerCall(uint target, bytes calldata request) pure returns (bytes
20
21
  /// @notice Abstract base for all rootzero peer contracts.
21
22
  /// Peers handle inter-host operations and asset allow/deny management
22
23
  /// between cooperating hosts. Access is restricted to trusted callers via `onlyPeer`.
23
- abstract contract PeerBase is NodeCalls, PeerEvent {
24
+ abstract contract PeerBase is NodeCalls, PeerEvent, LabeledEvent {
24
25
  /// @dev Thrown when the commander attempts to call a peer entrypoint directly.
25
26
  error CommanderNotAllowed();
26
27
 
@@ -31,10 +32,10 @@ abstract contract PeerBase is NodeCalls, PeerEvent {
31
32
  _;
32
33
  }
33
34
 
34
- /// @notice Derive the deterministic node ID for a named peer on this contract.
35
- /// @param name Peer function name (without argument list).
35
+ /// @notice Derive the deterministic node ID for a peer selector on this contract.
36
+ /// @param selector Peer entrypoint selector.
36
37
  /// @return Peer node ID.
37
- function peerId(string memory name) internal view returns (uint) {
38
- return Ids.toPeer(Selectors.peer(name), address(this));
38
+ function peerId(bytes4 selector) internal view returns (uint) {
39
+ return Ids.toPeer(selector, address(this));
39
40
  }
40
41
  }
@@ -0,0 +1,39 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import { PeerBase } from "./Base.sol";
5
+ import { CreditAccountHook } from "../commands/Credit.sol";
6
+ import { Cursors, Cur, Forms } from "../Cursors.sol";
7
+
8
+ using Cursors for Cur;
9
+
10
+ interface IPeerCreditTo {
11
+ function peerCreditTo(bytes calldata request) external returns (bytes memory);
12
+ }
13
+
14
+ /// @title PeerCreditTo
15
+ /// @notice Peer that lets a trusted peer credit supplied accounts directly.
16
+ /// Each ACCOUNT_AMOUNT block calls `creditAccount` for its account.
17
+ abstract contract PeerCreditTo is PeerBase, CreditAccountHook, IPeerCreditTo {
18
+ uint internal immutable peerCreditToId = peerId(this.peerCreditTo.selector);
19
+
20
+ constructor() {
21
+ emit Peer(host, peerCreditToId, "1:0", Forms.AccountAmount, "", false);
22
+ emit Labeled(peerCreditToId, bytes32(0), "peerCreditTo");
23
+ }
24
+
25
+ /// @notice Execute the peer-credit call.
26
+ /// @param request ACCOUNT_AMOUNT block stream supplied by the trusted peer.
27
+ /// @return Empty response bytes.
28
+ function peerCreditTo(bytes calldata request) external onlyPeer returns (bytes memory) {
29
+ (Cur memory amounts, , ) = Cursors.init(request, 1);
30
+
31
+ while (amounts.i < amounts.len) {
32
+ (bytes32 account, bytes32 asset, bytes32 meta, uint amount) = amounts.unpackAccountAmount();
33
+ creditAccount(account, asset, meta, amount);
34
+ }
35
+
36
+ amounts.complete();
37
+ return "";
38
+ }
39
+ }