@rootzero/contracts 1.6.0 → 1.8.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 (48) hide show
  1. package/CHANGELOG.md +54 -1
  2. package/Core.sol +1 -1
  3. package/Endpoints.sol +15 -15
  4. package/Events.sol +2 -1
  5. package/README.md +9 -9
  6. package/blocks/Cursors.sol +36 -49
  7. package/blocks/Keys.sol +5 -5
  8. package/blocks/Schema.sol +4 -2
  9. package/blocks/Writers.sol +0 -35
  10. package/commands/Deposit.sol +2 -2
  11. package/commands/Provision.sol +2 -2
  12. package/commands/Recover.sol +48 -0
  13. package/commands/Relay.sol +4 -4
  14. package/commands/admin/Execute.sol +2 -4
  15. package/core/Calls.sol +74 -38
  16. package/core/Commitments.sol +1 -1
  17. package/core/Payable.sol +15 -28
  18. package/core/Pipeline.sol +1 -1
  19. package/docs/Schema.md +13 -2
  20. package/events/Commitment.sol +4 -4
  21. package/events/Dispatch.sol +20 -0
  22. package/events/Port.sol +22 -0
  23. package/guards/Base.sol +0 -11
  24. package/package.json +1 -1
  25. package/ports/AllowAssets.sol +40 -0
  26. package/ports/Allowance.sol +37 -0
  27. package/ports/Base.sol +30 -0
  28. package/ports/Credit.sol +35 -0
  29. package/ports/Debit.sol +35 -0
  30. package/ports/DenyAssets.sol +40 -0
  31. package/{peer → ports}/Dispatch.sol +11 -15
  32. package/ports/Pipe.sol +39 -0
  33. package/{peer → ports}/Redeem.sol +11 -15
  34. package/ports/Settle.sol +37 -0
  35. package/queries/Base.sol +0 -11
  36. package/utils/Layout.sol +3 -3
  37. package/utils/Nodes.sol +21 -21
  38. package/utils/Value.sol +12 -5
  39. package/events/Peer.sol +0 -22
  40. package/peer/AllowAssets.sol +0 -44
  41. package/peer/Allowance.sol +0 -41
  42. package/peer/Base.sol +0 -41
  43. package/peer/Credit.sol +0 -39
  44. package/peer/Debit.sol +0 -39
  45. package/peer/DenyAssets.sol +0 -44
  46. package/peer/Pipe.sol +0 -44
  47. package/peer/Recover.sol +0 -51
  48. package/peer/Settle.sol +0 -41
package/core/Calls.sol CHANGED
@@ -12,7 +12,7 @@ import {Nodes} from "../utils/Nodes.sol";
12
12
  error FailedCall(address addr, bytes4 selector, bytes err);
13
13
 
14
14
  /// @title NodeCalls
15
- /// @notice Shared trusted inter-node call helpers for contracts that can talk to other nodes.
15
+ /// @notice Shared low-level inter-node call helpers for contracts that can talk to other nodes.
16
16
  abstract contract NodeCalls is AccessControl {
17
17
  /// @notice Return the host node ID corresponding to the current caller.
18
18
  /// @dev Encodes `msg.sender` as a host ID using the local-chain host layout.
@@ -21,64 +21,100 @@ abstract contract NodeCalls is AccessControl {
21
21
  return Nodes.toHost(msg.sender);
22
22
  }
23
23
 
24
- /// @notice Make a low-level call to an address.
25
- /// Forwards `value` ETH and `data` to `addr`.
26
- /// Reverts with `FailedCall` if the call is unsuccessful.
27
- /// @param addr Contract address to call.
24
+ /// @notice Try a raw low-level call to another node and return whether it succeeded.
25
+ /// @param node Node ID of the callee.
28
26
  /// @param value Native value to forward in wei.
29
27
  /// @param data Encoded calldata to send.
30
- /// @return out Return data from the successful call.
31
- function callAddr(address addr, uint128 value, bytes memory data) internal returns (bytes memory out) {
32
- bool success;
33
- (success, out) = payable(addr).call{value: value}(data);
34
- if (!success) revert FailedCall(addr, bytes4(data), out);
28
+ /// @return success True if the low-level call succeeded.
29
+ function tryRawCall(uint node, uint128 value, bytes memory data) internal returns (bool success) {
30
+ address addr = Nodes.addr(node);
31
+ (success, ) = payable(addr).call{value: value}(data);
35
32
  }
36
33
 
37
- /// @notice Make a low-level read-only query to an address.
38
- /// Issues a low-level `staticcall` with `data`.
39
- /// Reverts with `FailedCall` if the call is unsuccessful.
40
- /// @param addr Contract address to query.
34
+ /// @notice Try a trusted low-level call to another node and return whether it succeeded.
35
+ /// @param node Node ID of the callee.
36
+ /// @param value Native value to forward in wei.
41
37
  /// @param data Encoded calldata to send.
42
- /// @return out Return data from the successful query.
43
- function queryAddr(address addr, bytes memory data) internal view returns (bytes memory out) {
38
+ /// @return success True if the low-level call succeeded.
39
+ function tryTrustedCall(uint node, uint128 value, bytes memory data) internal returns (bool success) {
40
+ return tryRawCall(ensureTrusted(node), value, data);
41
+ }
42
+
43
+ /// @notice Make a raw low-level call to another node and revert when it fails.
44
+ /// @param node Node ID of the callee.
45
+ /// @param value Native value to forward in wei.
46
+ /// @param data Encoded calldata to send.
47
+ /// @return out Return data from the successful call.
48
+ function rawCall(uint node, uint128 value, bytes memory data) internal returns (bytes memory out) {
44
49
  bool success;
45
- (success, out) = addr.staticcall(data);
50
+ address addr = Nodes.addr(node);
51
+ (success, out) = payable(addr).call{value: value}(data);
46
52
  if (!success) revert FailedCall(addr, bytes4(data), out);
47
53
  }
48
54
 
49
- /// @notice Make a trusted call to another node in the network.
50
- /// Looks up the node's contract address via `ensureTrusted` + `Nodes.addr`,
51
- /// then issues a low-level call forwarding `value` ETH and `data`.
52
- /// @param node Node ID of the callee (must be in the authorized set).
55
+ /// @notice Make a trusted low-level call to another node and revert when it fails.
56
+ /// @param node Node ID of the callee.
53
57
  /// @param value Native value to forward in wei.
54
58
  /// @param data Encoded calldata to send.
55
59
  /// @return out Return data from the successful call.
56
- function callTo(uint node, uint128 value, bytes memory data) internal returns (bytes memory out) {
57
- ensureTrusted(node);
58
- address addr = Nodes.addr(node);
59
- return callAddr(addr, value, data);
60
+ function trustedCall(uint node, uint128 value, bytes memory data) internal returns (bytes memory out) {
61
+ return rawCall(ensureTrusted(node), value, data);
60
62
  }
61
63
 
62
- /// @notice Make a trusted query to another node in the network.
63
- /// Looks up the node's contract address via `ensureTrusted` + `Nodes.addr`,
64
- /// then issues a low-level `staticcall` with `data`.
65
- /// @param node Node ID of the callee (must be in the authorized set).
64
+ /// @notice Make a raw low-level read-only query to another node and revert when it fails.
65
+ /// @param node Node ID of the callee.
66
66
  /// @param data Encoded calldata to send.
67
67
  /// @return out Return data from the successful query.
68
- function queryTo(uint node, bytes memory data) internal view returns (bytes memory out) {
69
- ensureTrusted(node);
68
+ function rawQuery(uint node, bytes memory data) internal view returns (bytes memory out) {
69
+ bool success;
70
70
  address addr = Nodes.addr(node);
71
- return queryAddr(addr, data);
71
+ (success, out) = addr.staticcall(data);
72
+ if (!success) revert FailedCall(addr, bytes4(data), out);
73
+ }
74
+
75
+ /// @notice Make a trusted low-level read-only query to another node and revert when it fails.
76
+ /// @param node Node ID of the callee.
77
+ /// @param data Encoded calldata to send.
78
+ /// @return out Return data from the successful query.
79
+ function trustedQuery(uint node, bytes memory data) internal view returns (bytes memory out) {
80
+ return rawQuery(ensureTrusted(node), data);
72
81
  }
82
+ }
73
83
 
84
+ /// @title CommandCalls
85
+ /// @notice Trusted command-call helpers for contracts that route command nodes.
86
+ abstract contract CommandCalls is NodeCalls {
74
87
  /// @notice Encode and call a trusted command node.
75
- /// @param id Command node ID embedding the target selector.
88
+ /// @param command Command node ID embedding the target selector.
76
89
  /// @param value Native value to forward in wei.
77
- /// @param ctx Command execution context.
90
+ /// @param account Command account identifier.
91
+ /// @param state Current command state block stream.
92
+ /// @param request Command input block stream.
78
93
  /// @return Decoded command output block stream.
79
- function callCommand(uint id, uint128 value, CommandContext memory ctx) internal returns (bytes memory) {
80
- bytes4 selector = Nodes.commandSelector(id);
81
- bytes memory data = abi.encodeWithSelector(selector, ctx);
82
- return abi.decode(callTo(id, value, data), (bytes));
94
+ function callCommand(
95
+ uint command,
96
+ uint128 value,
97
+ bytes32 account,
98
+ bytes memory state,
99
+ bytes calldata request
100
+ ) internal returns (bytes memory) {
101
+ bytes4 selector = Nodes.commandSelector(command);
102
+ bytes memory data = abi.encodeWithSelector(selector, CommandContext(account, state, request));
103
+ return abi.decode(trustedCall(command, value, data), (bytes));
104
+ }
105
+ }
106
+
107
+ /// @title PortCalls
108
+ /// @notice Trusted port-call helpers for contracts that route port nodes.
109
+ abstract contract PortCalls is NodeCalls {
110
+ /// @notice Encode and call a trusted port node.
111
+ /// @param port Port node ID embedding the target selector.
112
+ /// @param value Native value to forward in wei.
113
+ /// @param input Port input block stream.
114
+ /// @return Decoded port output block stream.
115
+ function callPort(uint port, uint128 value, bytes memory input) internal returns (bytes memory) {
116
+ bytes4 selector = Nodes.portSelector(port);
117
+ bytes memory data = abi.encodeWithSelector(selector, input);
118
+ return abi.decode(trustedCall(port, value, data), (bytes));
83
119
  }
84
120
  }
@@ -4,7 +4,7 @@ pragma solidity ^0.8.33;
4
4
  import {CommitmentEvent} from "../events/Commitment.sol";
5
5
 
6
6
  /// @title Commitments
7
- /// @notice On-chain registry for account-scoped digest commitments.
7
+ /// @notice On-chain registry for digest commitments.
8
8
  abstract contract Commitments is CommitmentEvent {
9
9
  /// @dev key -> committed digest.
10
10
  mapping(bytes32 key => bytes32 digest) internal commitments;
package/core/Payable.sol CHANGED
@@ -5,16 +5,14 @@ import {Budget, Values} from "../utils/Value.sol";
5
5
 
6
6
  /// @title Payable
7
7
  /// @notice Abstract mixin for entrypoints that accept native value (`msg.value`).
8
- /// Provides a shared settlement hook for any unspent value remaining in the
9
- /// mutable budget after execution completes.
8
+ /// Provides shared helpers for mutable native-value budgets.
10
9
  abstract contract Payable {
11
10
  /// @dev Thrown when a payable entrypoint completes with unspent native value.
12
- /// Override `settleValue` to implement refund or forwarding behavior instead.
13
11
  error UnusedValue(uint remaining);
14
12
 
15
- /// @notice Create a native-value budget from the current call's `msg.value`.
16
- /// @return Budget initialised with the full `msg.value`.
17
- function valueBudget() internal view returns (Budget memory) {
13
+ /// @notice Open a native-value budget from the current call's `msg.value`.
14
+ /// @return Budget initialized with the full `msg.value`.
15
+ function openValue() internal view returns (Budget memory) {
18
16
  return Budget({remaining: msg.value});
19
17
  }
20
18
 
@@ -24,36 +22,25 @@ abstract contract Payable {
24
22
  /// @param resources Packed chain resources.
25
23
  /// @return value Native value to forward in wei.
26
24
  function useValue(Budget memory budget, uint resources) internal pure returns (uint128 value) {
27
- return Values.use(budget, uint128(resources));
25
+ value = uint128(resources);
26
+ Values.use(budget, value);
28
27
  }
29
28
 
30
- /// @notice Deduct the EVM value lane from a packed resource word as a new sub-budget.
31
- /// @dev EVM resources use the low 128 bits as native value/endowment.
32
- /// @param budget Mutable parent budget to deduct from.
33
- /// @param resources Packed chain resources.
34
- /// @return A new budget with the EVM value lane remaining.
35
- function allocateValue(Budget memory budget, uint resources) internal pure returns (Budget memory) {
36
- return Values.allocate(budget, uint128(resources));
37
- }
38
-
39
- /// @notice Drains the budget and settles any remaining native value.
40
- /// @dev Calls the amount-based `settleValue` hook only when some value remains.
29
+ /// @notice Close a native-value budget and settle any drained value.
41
30
  /// @param account Account identifier for the current invocation.
42
- /// @param budget Mutable native-value budget used during execution.
43
- function settleValue(bytes32 account, Budget memory budget) internal {
44
- uint value = budget.remaining;
31
+ /// @param budget Mutable native-value budget to close.
32
+ function closeValue(bytes32 account, Budget memory budget) internal {
33
+ uint value = Values.drain(budget);
45
34
  if (value == 0) return;
46
- budget.remaining = 0;
47
35
  settleValue(account, value);
48
36
  }
49
37
 
50
- /// @notice Handles leftover native value after payable execution has finished.
51
- /// @dev Override this hook to refund or redirect unused value.
52
- /// The default implementation rejects any leftover amount.
38
+ /// @notice Handle a drained native value amount.
39
+ /// @dev Override to refund or redirect unused value. The default rejects it.
53
40
  /// @param account Account identifier for the current invocation.
54
- /// @param remaining Unspent native value left in the budget, in wei.
55
- function settleValue(bytes32 account, uint remaining) internal virtual {
41
+ /// @param value Drained native value amount to settle, in wei.
42
+ function settleValue(bytes32 account, uint value) internal virtual {
56
43
  account;
57
- revert UnusedValue(remaining);
44
+ revert UnusedValue(value);
58
45
  }
59
46
  }
package/core/Pipeline.sol CHANGED
@@ -32,6 +32,7 @@ abstract contract Pipeline is Payable {
32
32
 
33
33
  /// @notice Execute a STEP block stream through the pipeline.
34
34
  /// @dev Reverts with `UnexpectedState` if the final threaded state is non-empty.
35
+ /// Callers remain responsible for settling any unspent value in `budget`.
35
36
  /// @param account Account identifier used for each dispatched step.
36
37
  /// @param state Initial state block stream passed to the first step.
37
38
  /// @param steps STEP block stream to execute.
@@ -50,7 +51,6 @@ abstract contract Pipeline is Payable {
50
51
  }
51
52
 
52
53
  if (state.length != 0) revert UnexpectedState();
53
- settleValue(account, budget);
54
54
  input.complete();
55
55
  }
56
56
  }
package/docs/Schema.md CHANGED
@@ -58,7 +58,6 @@ Once a child block appears, no more fixed fields may follow.
58
58
  ```txt
59
59
  #call { uint target, uint resources, #bytes as payload }
60
60
  #context { bytes32 account, #bytes as state, #bytes as request }
61
- #pipe { uint resources, #context { bytes32 account, #bytes as state, #bytes as steps } }
62
61
  ```
63
62
 
64
63
  The tail is embedded directly as child block bytes. There is no wrapper around a
@@ -118,6 +117,18 @@ maybe #account { bytes32 account } as recipient
118
117
 
119
118
  Aliases may be used on any block item, including child blocks and prime items.
120
119
 
120
+ A child block without an inline body may also be used as a schema reference:
121
+
122
+ ```txt
123
+ #contextRecovery { uint port, bytes32 key, uint resources, #context as witness }
124
+ ```
125
+
126
+ Alias resolution is context-dependent. A consumer may resolve `#context` from the
127
+ standard `Schemas` table, from app-specific schemas, or from another active
128
+ schema context. Consumers should reject schemas with unresolved aliases. The
129
+ runtime encoding is still an embedded child block with the referenced key and
130
+ layout.
131
+
121
132
  ## Field Paths
122
133
 
123
134
  Field names and aliases may use dotted paths for offchain projection. A dotted
@@ -271,7 +282,7 @@ Common protocol schemas live in `contracts/blocks/Schema.sol`:
271
282
  #call { uint target, uint resources, #bytes as payload }
272
283
  #step { uint target, uint resources, #bytes as request }
273
284
  #context { bytes32 account, #bytes as state, #bytes as request }
274
- #pipe { uint resources, #context { bytes32 account, #bytes as state, #bytes as steps } }
285
+ #contextRecovery { uint port, bytes32 key, uint resources, #context as witness }
275
286
  #auth { uint cid, uint deadline, #bytes as proof }
276
287
  ```
277
288
 
@@ -3,15 +3,15 @@ pragma solidity ^0.8.33;
3
3
 
4
4
  import { EventEmitter } from "./Emitter.sol";
5
5
 
6
- /// @notice Emitted when an account-scoped commitment is updated.
6
+ /// @notice Emitted when a host-scoped commitment is updated.
7
7
  abstract contract CommitmentEvent is EventEmitter {
8
- string private constant ABI = "event Commitment(bytes32 indexed account, bytes32 key, bytes32 digest, uint status)";
8
+ string private constant ABI = "event Commitment(uint indexed host, bytes32 key, bytes32 digest, uint status)";
9
9
 
10
- /// @param account Account associated with the commitment.
10
+ /// @param host Host node ID that manages the commitment.
11
11
  /// @param key Commitment lookup key.
12
12
  /// @param digest Committed digest. Zero may be used when clearing without revealing the previous digest.
13
13
  /// @param status Commitment status. Zero means cleared; nonzero means committed or application-defined.
14
- event Commitment(bytes32 indexed account, bytes32 key, bytes32 digest, uint status);
14
+ event Commitment(uint indexed host, bytes32 key, bytes32 digest, uint status);
15
15
 
16
16
  constructor() {
17
17
  emit EventAbi(ABI);
@@ -0,0 +1,20 @@
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 host records an outbound dispatch reference.
7
+ abstract contract DispatchEvent is EventEmitter {
8
+ string private constant ABI = "event Dispatch(uint indexed host, uint chain, uint resources, bytes32 digest, bytes32 ref)";
9
+
10
+ /// @param host Host node ID that owns the dispatch.
11
+ /// @param chain Destination chain/domain node ID.
12
+ /// @param resources Chain-adapter-specific resources assigned to the dispatch.
13
+ /// @param digest Digest of the dispatched payload or canonical envelope.
14
+ /// @param ref Dispatch correlation or recovery reference.
15
+ event Dispatch(uint indexed host, uint chain, uint resources, bytes32 digest, bytes32 ref);
16
+
17
+ constructor() {
18
+ emit EventAbi(ABI);
19
+ }
20
+ }
@@ -0,0 +1,22 @@
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 by hosts to advertise callable peer-facing ports.
7
+ abstract contract PortEvent is EventEmitter {
8
+ string private constant ABI =
9
+ "event Port(uint indexed host, uint id, bytes32 shape, string request, string response, bool funded)";
10
+
11
+ /// @param host Host node ID that exposes the port.
12
+ /// @param id Port node ID.
13
+ /// @param shape Block shape/version descriptor.
14
+ /// @param request Human-readable request schema.
15
+ /// @param response Human-readable response schema.
16
+ /// @param funded True if the port accepts native value.
17
+ event Port(uint indexed host, uint id, bytes32 shape, string request, string response, bool funded);
18
+
19
+ constructor() {
20
+ emit EventAbi(ABI);
21
+ }
22
+ }
package/guards/Base.sol CHANGED
@@ -6,17 +6,6 @@ import {GuardEvent} from "../events/Guard.sol";
6
6
  import {LabeledEvent} from "../events/Labeled.sol";
7
7
  import {Nodes} from "../utils/Nodes.sol";
8
8
 
9
- /// @notice ABI-encode a guard action call from a target guard ID and request block stream.
10
- /// @dev Derives the function selector from `target` via `Nodes.guardSelector(target)`.
11
- /// Reverts if `target` is not a valid guard ID.
12
- /// @param target Destination guard action node ID embedding the target selector.
13
- /// @param request Input block stream for the guard invocation.
14
- /// @return ABI-encoded calldata for the guard action entry point.
15
- function encodeGuardCall(uint target, bytes calldata request) pure returns (bytes memory) {
16
- bytes4 selector = Nodes.guardSelector(target);
17
- return abi.encodeWithSelector(selector, request);
18
- }
19
-
20
9
  /// @title GuardBase
21
10
  /// @notice Abstract base for guardian-only direct host actions.
22
11
  /// Guard actions are non-payable direct calls with no command context, state, or response.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rootzero/contracts",
3
- "version": "1.6.0",
3
+ "version": "1.8.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",
@@ -0,0 +1,40 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import { PortBase } from "./Base.sol";
5
+ import { AllowAssetsHook } from "../commands/admin/AllowAssets.sol";
6
+ import { Cursors, Cur, Schemas } from "../Cursors.sol";
7
+
8
+ using Cursors for Cur;
9
+
10
+ /// @title PortAllowAssets
11
+ /// @notice Port that permits a list of assets on behalf of a peer host.
12
+ /// Each ASSET block in the request calls `allowAsset`. Restricted to trusted peers.
13
+ abstract contract PortAllowAssets is PortBase, AllowAssetsHook {
14
+ uint internal immutable portAllowAssetsId = portId(this.portAllowAssets.selector);
15
+
16
+ constructor() {
17
+ emit Port(host, portAllowAssetsId, "1:0", Schemas.Asset, "", false);
18
+ emit Labeled(portAllowAssetsId, bytes32(0), "portAllowAssets");
19
+ }
20
+
21
+ /// @notice Execute the allow-assets peer call.
22
+ /// @param data ASSET block stream supplied by the trusted peer.
23
+ /// @return Empty response bytes.
24
+ function portAllowAssets(bytes calldata data) external onlyPeer returns (bytes memory) {
25
+ (Cur memory assets, , ) = Cursors.init(data, 1);
26
+
27
+ while (assets.i < assets.len) {
28
+ bytes32 asset = assets.unpackAsset();
29
+ allowAsset(asset);
30
+ }
31
+
32
+ assets.complete();
33
+ return "";
34
+ }
35
+ }
36
+
37
+
38
+
39
+
40
+
@@ -0,0 +1,37 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import {PortBase} from "./Base.sol";
5
+ import {AllowanceHook} from "../commands/admin/Allowance.sol";
6
+ import {Cursors, Cur, Schemas} from "../Cursors.sol";
7
+
8
+ using Cursors for Cur;
9
+
10
+ /// @title PortAllowance
11
+ /// @notice Port that lets a trusted peer host request or refresh its own allowance.
12
+ /// Each AMOUNT block in the request is scoped to the peer host and passed to the
13
+ /// shared allowance hook as a host-scoped allowance. Restricted to trusted peers.
14
+ abstract contract PortAllowance is PortBase, AllowanceHook {
15
+ uint internal immutable portAllowanceId = portId(this.portAllowance.selector);
16
+
17
+ constructor() {
18
+ emit Port(host, portAllowanceId, "1:0", Schemas.Amount, "", false);
19
+ emit Labeled(portAllowanceId, bytes32(0), "portAllowance");
20
+ }
21
+
22
+ /// @notice Execute the allowance port call.
23
+ /// @param data AMOUNT block stream requested by the trusted peer.
24
+ /// @return Empty response bytes.
25
+ function portAllowance(bytes calldata data) external onlyPeer returns (bytes memory) {
26
+ (Cur memory amounts, , ) = Cursors.init(data, 1);
27
+ uint peer = caller();
28
+
29
+ while (amounts.i < amounts.len) {
30
+ (bytes32 asset, uint amount) = amounts.unpackAmount();
31
+ allowance(peer, asset, amount);
32
+ }
33
+
34
+ amounts.complete();
35
+ return "";
36
+ }
37
+ }
package/ports/Base.sol ADDED
@@ -0,0 +1,30 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import { NodeCalls } from "../core/Calls.sol";
5
+ import { PortEvent } from "../events/Port.sol";
6
+ import { LabeledEvent } from "../events/Labeled.sol";
7
+ import { Nodes } from "../utils/Nodes.sol";
8
+
9
+ /// @title PortBase
10
+ /// @notice Abstract base for peer-facing rootzero ports.
11
+ /// Ports handle inter-host operations between cooperating hosts.
12
+ /// Access is restricted to trusted peer callers via `onlyPeer`.
13
+ abstract contract PortBase is NodeCalls, PortEvent, LabeledEvent {
14
+ /// @dev Thrown when the commander attempts to call a port entrypoint directly.
15
+ error CommanderNotAllowed();
16
+
17
+ /// @dev Restrict execution to trusted callers, excluding the commander.
18
+ modifier onlyPeer() {
19
+ if (msg.sender == commander) revert CommanderNotAllowed();
20
+ enforceCaller(msg.sender);
21
+ _;
22
+ }
23
+
24
+ /// @notice Derive the deterministic node ID for a port selector on this contract.
25
+ /// @param selector Port entrypoint selector.
26
+ /// @return Port node ID.
27
+ function portId(bytes4 selector) internal view returns (uint) {
28
+ return Nodes.toPort(selector, address(this));
29
+ }
30
+ }
@@ -0,0 +1,35 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import { PortBase } 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
+ /// @title PortCreditAccount
11
+ /// @notice Port that lets a trusted peer credit supplied accounts directly.
12
+ /// Each ACCOUNT_AMOUNT block calls `creditAccount` for its account.
13
+ abstract contract PortCreditAccount is PortBase, CreditAccountHook {
14
+ uint internal immutable portCreditAccountId = portId(this.portCreditAccount.selector);
15
+
16
+ constructor() {
17
+ emit Port(host, portCreditAccountId, "1:0", Forms.AccountAmount, "", false);
18
+ emit Labeled(portCreditAccountId, bytes32(0), "portCreditAccount");
19
+ }
20
+
21
+ /// @notice Execute the port-credit call.
22
+ /// @param data ACCOUNT_AMOUNT block stream supplied by the trusted peer.
23
+ /// @return Empty response bytes.
24
+ function portCreditAccount(bytes calldata data) external onlyPeer returns (bytes memory) {
25
+ (Cur memory amounts, , ) = Cursors.init(data, 1);
26
+
27
+ while (amounts.i < amounts.len) {
28
+ (bytes32 account, bytes32 asset, uint amount) = amounts.unpackAccountAmount();
29
+ creditAccount(account, asset, amount);
30
+ }
31
+
32
+ amounts.complete();
33
+ return "";
34
+ }
35
+ }
@@ -0,0 +1,35 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import { PortBase } from "./Base.sol";
5
+ import { DebitAccountHook } from "../commands/Debit.sol";
6
+ import { Cursors, Cur, Forms } from "../Cursors.sol";
7
+
8
+ using Cursors for Cur;
9
+
10
+ /// @title PortDebitAccount
11
+ /// @notice Port that lets a trusted peer debit supplied accounts directly.
12
+ /// Each ACCOUNT_AMOUNT block calls `debitAccount` for its account.
13
+ abstract contract PortDebitAccount is PortBase, DebitAccountHook {
14
+ uint internal immutable portDebitAccountId = portId(this.portDebitAccount.selector);
15
+
16
+ constructor() {
17
+ emit Port(host, portDebitAccountId, "1:0", Forms.AccountAmount, "", false);
18
+ emit Labeled(portDebitAccountId, bytes32(0), "portDebitAccount");
19
+ }
20
+
21
+ /// @notice Execute the port-debit call.
22
+ /// @param data ACCOUNT_AMOUNT block stream supplied by the trusted peer.
23
+ /// @return Empty response bytes.
24
+ function portDebitAccount(bytes calldata data) external onlyPeer returns (bytes memory) {
25
+ (Cur memory amounts, , ) = Cursors.init(data, 1);
26
+
27
+ while (amounts.i < amounts.len) {
28
+ (bytes32 account, bytes32 asset, uint amount) = amounts.unpackAccountAmount();
29
+ debitAccount(account, asset, amount);
30
+ }
31
+
32
+ amounts.complete();
33
+ return "";
34
+ }
35
+ }
@@ -0,0 +1,40 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import {PortBase} from "./Base.sol";
5
+ import {DenyAssetsHook} from "../commands/admin/DenyAssets.sol";
6
+ import {Cursors, Cur, Schemas} from "../Cursors.sol";
7
+
8
+ using Cursors for Cur;
9
+
10
+ /// @title PortDenyAssets
11
+ /// @notice Port that blocks a list of assets on behalf of a peer host.
12
+ /// Each ASSET block in the request calls `denyAsset`. Restricted to trusted peers.
13
+ abstract contract PortDenyAssets is PortBase, DenyAssetsHook {
14
+ uint internal immutable portDenyAssetsId = portId(this.portDenyAssets.selector);
15
+
16
+ constructor() {
17
+ emit Port(host, portDenyAssetsId, "1:0", Schemas.Asset, "", false);
18
+ emit Labeled(portDenyAssetsId, bytes32(0), "portDenyAssets");
19
+ }
20
+
21
+ /// @notice Execute the deny-assets peer call.
22
+ /// @param data ASSET block stream supplied by the trusted peer.
23
+ /// @return Empty response bytes.
24
+ function portDenyAssets(bytes calldata data) external onlyPeer returns (bytes memory) {
25
+ (Cur memory assets, , ) = Cursors.init(data, 1);
26
+
27
+ while (assets.i < assets.len) {
28
+ bytes32 asset = assets.unpackAsset();
29
+ denyAsset(asset);
30
+ }
31
+
32
+ assets.complete();
33
+ return "";
34
+ }
35
+ }
36
+
37
+
38
+
39
+
40
+
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import { PeerBase } from "./Base.sol";
4
+ import { PortBase } from "./Base.sol";
5
5
  import { Payable } from "../core/Payable.sol";
6
6
  import { Cursors, Cur, Schemas } from "../Cursors.sol";
7
7
  import { DispatchPayableHook } from "../commands/Relay.sol";
@@ -9,28 +9,24 @@ import { Budget } from "../utils/Value.sol";
9
9
 
10
10
  using Cursors for Cur;
11
11
 
12
- interface IPeerDispatchPayable {
13
- function peerDispatchPayable(bytes calldata request) external payable returns (bytes memory);
14
- }
15
-
16
- /// @title PeerDispatchPayable
17
- /// @notice Peer endpoint that forwards DISPATCH blocks to a host-defined dispatch hook.
18
- abstract contract PeerDispatchPayable is PeerBase, Payable, DispatchPayableHook, IPeerDispatchPayable {
19
- uint internal immutable peerDispatchPayableId = peerId(this.peerDispatchPayable.selector);
12
+ /// @title PortDispatchPayable
13
+ /// @notice Port endpoint that forwards DISPATCH blocks to a host-defined dispatch hook.
14
+ abstract contract PortDispatchPayable is PortBase, Payable, DispatchPayableHook {
15
+ uint internal immutable portDispatchPayableId = portId(this.portDispatchPayable.selector);
20
16
 
21
17
  constructor() {
22
- emit Peer(host, peerDispatchPayableId, "1:0", Schemas.Dispatch, "", true);
23
- emit Labeled(peerDispatchPayableId, bytes32(0), "peerDispatchPayable");
18
+ emit Port(host, portDispatchPayableId, "1:0", Schemas.Dispatch, "", true);
19
+ emit Labeled(portDispatchPayableId, bytes32(0), "portDispatchPayable");
24
20
  }
25
21
 
26
22
  /// @notice Forward peer-supplied dispatches to the host-defined dispatch hook.
27
23
  /// @dev Dispatch hooks receive the shared top-level source-chain value
28
24
  /// budget. Any `msg.value` not spent by the hook remains on this host.
29
- /// @param request DISPATCH block stream supplied by the trusted peer.
25
+ /// @param data DISPATCH block stream supplied by the trusted peer.
30
26
  /// @return output Empty response bytes.
31
- function peerDispatchPayable(bytes calldata request) external payable onlyPeer returns (bytes memory output) {
32
- (Cur memory input, , ) = Cursors.init(request, 1);
33
- Budget memory budget = valueBudget();
27
+ function portDispatchPayable(bytes calldata data) external payable onlyPeer returns (bytes memory output) {
28
+ (Cur memory input, , ) = Cursors.init(data, 1);
29
+ Budget memory budget = openValue();
34
30
 
35
31
  while (input.i < input.len) {
36
32
  (uint chain, uint resources, bytes calldata payload) = input.unpackDispatch();