@rootzero/contracts 1.5.0 → 1.7.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 (58) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/Core.sol +4 -2
  3. package/Endpoints.sol +14 -13
  4. package/Events.sol +3 -1
  5. package/README.md +9 -9
  6. package/blocks/Cursors.sol +60 -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/AllowAssets.sol +2 -3
  15. package/commands/admin/Allowance.sol +2 -3
  16. package/commands/admin/Appoint.sol +2 -3
  17. package/commands/admin/Authorize.sol +2 -3
  18. package/commands/admin/Base.sol +9 -0
  19. package/commands/admin/DenyAssets.sol +2 -3
  20. package/commands/admin/Destroy.sol +2 -3
  21. package/commands/admin/Dismiss.sol +2 -3
  22. package/commands/admin/Execute.sol +3 -4
  23. package/commands/admin/Init.sol +2 -3
  24. package/commands/admin/Label.sol +2 -3
  25. package/commands/admin/Unauthorize.sol +2 -3
  26. package/core/Commitments.sol +19 -0
  27. package/core/Escrows.sol +34 -0
  28. package/core/Payable.sol +15 -28
  29. package/core/Pipeline.sol +1 -1
  30. package/core/Runtime.sol +8 -3
  31. package/docs/Schema.md +13 -2
  32. package/events/Commitment.sol +19 -0
  33. package/events/Dispatch.sol +20 -0
  34. package/events/Port.sol +22 -0
  35. package/package.json +1 -1
  36. package/ports/AllowAssets.sol +44 -0
  37. package/ports/Allowance.sol +41 -0
  38. package/ports/Base.sol +41 -0
  39. package/ports/Credit.sol +39 -0
  40. package/ports/Debit.sol +39 -0
  41. package/ports/DenyAssets.sol +44 -0
  42. package/{peer → ports}/Dispatch.sol +13 -13
  43. package/ports/Pipe.sol +43 -0
  44. package/{peer → ports}/Redeem.sol +13 -13
  45. package/ports/Settle.sol +41 -0
  46. package/utils/Layout.sol +3 -3
  47. package/utils/Nodes.sol +21 -21
  48. package/utils/Value.sol +12 -5
  49. package/events/Peer.sol +0 -22
  50. package/peer/AllowAssets.sol +0 -44
  51. package/peer/Allowance.sol +0 -41
  52. package/peer/Base.sol +0 -41
  53. package/peer/Credit.sol +0 -39
  54. package/peer/Debit.sol +0 -39
  55. package/peer/DenyAssets.sol +0 -44
  56. package/peer/Pipe.sol +0 -44
  57. package/peer/Recover.sol +0 -51
  58. package/peer/Settle.sol +0 -41
@@ -0,0 +1,9 @@
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 {AdminEvent} from "../../events/Admin.sol";
6
+
7
+ /// @title AdminBase
8
+ /// @notice Shared base for admin commands.
9
+ abstract contract AdminBase is CommandBase, AdminEvent {}
@@ -1,9 +1,8 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import { CommandBase, CommandContext, Keys } from "../Base.sol";
4
+ import { AdminBase, CommandContext, Keys } from "./Base.sol";
5
5
  import { Cursors, Cur, Schemas } from "../../Cursors.sol";
6
- import { AdminEvent } from "../../events/Admin.sol";
7
6
  using Cursors for Cur;
8
7
 
9
8
  abstract contract DenyAssetsHook {
@@ -16,7 +15,7 @@ abstract contract DenyAssetsHook {
16
15
  /// @title DenyAssets
17
16
  /// @notice Admin command that blocks a list of assets via a virtual hook.
18
17
  /// Each ASSET block in the request calls `denyAsset`. Only callable by the admin account.
19
- abstract contract DenyAssets is CommandBase, AdminEvent, DenyAssetsHook {
18
+ abstract contract DenyAssets is AdminBase, DenyAssetsHook {
20
19
  uint internal immutable denyAssetsId = commandId(this.denyAssets.selector);
21
20
 
22
21
  constructor() {
@@ -1,9 +1,8 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import { CommandBase, CommandContext, Keys } from "../Base.sol";
4
+ import { AdminBase, CommandContext, Keys } from "./Base.sol";
5
5
  import { Cursors, Cur } from "../../Cursors.sol";
6
- import { AdminEvent } from "../../events/Admin.sol";
7
6
 
8
7
  using Cursors for Cur;
9
8
 
@@ -16,7 +15,7 @@ abstract contract DestroyHook {
16
15
  /// @title Destroy
17
16
  /// @notice Admin command that runs host teardown logic via a virtual hook.
18
17
  /// The full request is passed to `destroy` as a cursor. Only callable by the admin account.
19
- abstract contract Destroy is CommandBase, AdminEvent, DestroyHook {
18
+ abstract contract Destroy is AdminBase, DestroyHook {
20
19
  uint internal immutable destroyId = commandId(this.destroy.selector);
21
20
 
22
21
  constructor(string memory input) {
@@ -1,16 +1,15 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import { CommandBase, CommandContext, Keys } from "../Base.sol";
4
+ import { AdminBase, CommandContext, Keys } from "./Base.sol";
5
5
  import { Cursors, Cur, Schemas } from "../../Cursors.sol";
6
- import { AdminEvent } from "../../events/Admin.sol";
7
6
  using Cursors for Cur;
8
7
 
9
8
  /// @title Dismiss
10
9
  /// @notice Admin command that revokes guardian status from a list of account IDs.
11
10
  /// Each ACCOUNT block in the request is disabled as a guardian on the host.
12
11
  /// Only callable by the admin account.
13
- abstract contract Dismiss is CommandBase, AdminEvent {
12
+ abstract contract Dismiss is AdminBase {
14
13
  uint internal immutable dismissId = commandId(this.dismiss.selector);
15
14
 
16
15
  constructor() {
@@ -1,10 +1,9 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import {CommandBase, CommandContext, Keys} from "../Base.sol";
4
+ import {AdminBase, CommandContext, Keys} from "./Base.sol";
5
5
  import {Payable} from "../../core/Payable.sol";
6
6
  import {Cursors, Cur, Schemas} from "../../Cursors.sol";
7
- import {AdminEvent} from "../../events/Admin.sol";
8
7
  import {Budget} from "../../utils/Value.sol";
9
8
  import {Nodes} from "../../utils/Nodes.sol";
10
9
 
@@ -15,7 +14,7 @@ using Cursors for Cur;
15
14
  /// Each CALL block specifies a target node ID, chain resources, and raw calldata payload.
16
15
  /// Only callable by the admin account.
17
16
  /// Unspent top-level `msg.value` remains on this host.
18
- abstract contract ExecutePayable is CommandBase, Payable, AdminEvent {
17
+ abstract contract ExecutePayable is AdminBase, Payable {
19
18
  uint internal immutable executePayableId = commandId(this.executePayable.selector);
20
19
 
21
20
  constructor() {
@@ -28,7 +27,7 @@ abstract contract ExecutePayable is CommandBase, Payable, AdminEvent {
28
27
  /// @return Empty output state.
29
28
  function executePayable(CommandContext calldata c) external payable onlyAdmin(c.account) returns (bytes memory) {
30
29
  (Cur memory request, , ) = Cursors.init(c.request, 1);
31
- Budget memory budget = valueBudget();
30
+ Budget memory budget = openValue();
32
31
 
33
32
  while (request.i < request.len) {
34
33
  (uint target, uint resources, bytes calldata data) = request.unpackCall();
@@ -1,9 +1,8 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import { CommandBase, CommandContext, Keys } from "../Base.sol";
4
+ import { AdminBase, CommandContext, Keys } from "./Base.sol";
5
5
  import { Cursors, Cur } from "../../Cursors.sol";
6
- import { AdminEvent } from "../../events/Admin.sol";
7
6
 
8
7
  using Cursors for Cur;
9
8
 
@@ -16,7 +15,7 @@ abstract contract InitHook {
16
15
  /// @title Init
17
16
  /// @notice Admin command that runs host initialization logic via a virtual hook.
18
17
  /// The full request is passed to `init` as a cursor. Only callable by the admin account.
19
- abstract contract Init is CommandBase, AdminEvent, InitHook {
18
+ abstract contract Init is AdminBase, InitHook {
20
19
  uint internal immutable initId = commandId(this.init.selector);
21
20
 
22
21
  constructor(string memory input) {
@@ -1,16 +1,15 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import {CommandBase, CommandContext, Keys} from "../Base.sol";
4
+ import {AdminBase, CommandContext, Keys} from "./Base.sol";
5
5
  import {Cursors, Cur, Schemas} from "../../Cursors.sol";
6
- import {AdminEvent} from "../../events/Admin.sol";
7
6
  using Cursors for Cur;
8
7
 
9
8
  /// @title Label
10
9
  /// @notice Admin command that publishes namespaced labels for node IDs.
11
10
  /// Each LABEL block in the request emits one `Labeled` event. Only callable by
12
11
  /// the admin account.
13
- abstract contract Label is CommandBase, AdminEvent {
12
+ abstract contract Label is AdminBase {
14
13
  uint internal immutable labelId = commandId(this.label.selector);
15
14
 
16
15
  constructor() {
@@ -1,16 +1,15 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import { CommandBase, CommandContext, Keys } from "../Base.sol";
4
+ import { AdminBase, CommandContext, Keys } from "./Base.sol";
5
5
  import { Cursors, Cur, Schemas } from "../../Cursors.sol";
6
- import { AdminEvent } from "../../events/Admin.sol";
7
6
  using Cursors for Cur;
8
7
 
9
8
  /// @title Unauthorize
10
9
  /// @notice Admin command that revokes authorization from a list of node IDs.
11
10
  /// Each NODE block in the request is deauthorized on the host.
12
11
  /// Only callable by the admin account.
13
- abstract contract Unauthorize is CommandBase, AdminEvent {
12
+ abstract contract Unauthorize is AdminBase {
14
13
  uint internal immutable unauthorizeId = commandId(this.unauthorize.selector);
15
14
 
16
15
  constructor() {
@@ -0,0 +1,19 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import {CommitmentEvent} from "../events/Commitment.sol";
5
+
6
+ /// @title Commitments
7
+ /// @notice On-chain registry for digest commitments.
8
+ abstract contract Commitments is CommitmentEvent {
9
+ /// @dev key -> committed digest.
10
+ mapping(bytes32 key => bytes32 digest) internal commitments;
11
+
12
+ /// @notice Clear the commitment under `key` and return the removed digest.
13
+ /// @param key Commitment lookup key.
14
+ /// @return digest Removed digest.
15
+ function uncommit(bytes32 key) internal returns (bytes32 digest) {
16
+ digest = commitments[key];
17
+ delete commitments[key];
18
+ }
19
+ }
@@ -0,0 +1,34 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ /// @dev Thrown when a debit would reduce an escrow below zero.
5
+ error InsufficientEscrow();
6
+
7
+ /// @title Escrows
8
+ /// @notice On-chain ledger for amounts reserved outside normal spendable balances.
9
+ abstract contract Escrows {
10
+ /// @dev key -> escrowed amount.
11
+ mapping(bytes32 key => uint amount) internal escrows;
12
+
13
+ /// @notice Add `amount` to an escrow and return the new balance.
14
+ /// @param key Escrow key derived by the caller.
15
+ /// @param amount Amount to reserve.
16
+ /// @return balance New escrow balance after the credit.
17
+ function creditEscrow(bytes32 key, uint amount) internal returns (uint balance) {
18
+ balance = escrows[key] += amount;
19
+ }
20
+
21
+ /// @notice Deduct `amount` from an escrow and return the new balance.
22
+ /// Reverts with `InsufficientEscrow` if the current balance is less than `amount`.
23
+ /// @param key Escrow key derived by the caller.
24
+ /// @param amount Amount to release.
25
+ /// @return balance New escrow balance after the debit.
26
+ function debitEscrow(bytes32 key, uint amount) internal returns (uint balance) {
27
+ balance = escrows[key];
28
+ if (balance < amount) revert InsufficientEscrow();
29
+ unchecked {
30
+ balance -= amount;
31
+ }
32
+ escrows[key] = balance;
33
+ }
34
+ }
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/core/Runtime.sol CHANGED
@@ -4,11 +4,16 @@ pragma solidity ^0.8.33;
4
4
  import {Assets} from "../utils/Assets.sol";
5
5
  import {Nodes} from "../utils/Nodes.sol";
6
6
 
7
+ /// @title NativeAsset
8
+ /// @notice Shared native asset identity for host helpers.
9
+ abstract contract NativeAsset {
10
+ /// @dev Asset ID for the native chain coin/token, bound to the current chain at deployment.
11
+ bytes32 internal immutable nativeAsset = Assets.toNative();
12
+ }
13
+
7
14
  /// @title Runtime
8
15
  /// @notice Shared runtime for host identity and native asset identity.
9
- abstract contract Runtime {
16
+ abstract contract Runtime is NativeAsset {
10
17
  /// @dev This contract's host node ID, set to `Nodes.toHost(address(this))` at construction.
11
18
  uint public immutable host = Nodes.toHost(address(this));
12
- /// @dev Asset ID for the native chain coin/token, bound to the current chain at deployment.
13
- bytes32 internal immutable nativeAsset = Assets.toNative();
14
19
  }
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
 
@@ -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 when a host-scoped commitment is updated.
7
+ abstract contract CommitmentEvent is EventEmitter {
8
+ string private constant ABI = "event Commitment(uint indexed host, bytes32 key, bytes32 digest, uint status)";
9
+
10
+ /// @param host Host node ID that manages the commitment.
11
+ /// @param key Commitment lookup key.
12
+ /// @param digest Committed digest. Zero may be used when clearing without revealing the previous digest.
13
+ /// @param status Commitment status. Zero means cleared; nonzero means committed or application-defined.
14
+ event Commitment(uint indexed host, bytes32 key, bytes32 digest, uint status);
15
+
16
+ constructor() {
17
+ emit EventAbi(ABI);
18
+ }
19
+ }
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rootzero/contracts",
3
- "version": "1.5.0",
3
+ "version": "1.7.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,44 @@
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
+ interface IPortAllowAssets {
11
+ function portAllowAssets(bytes calldata data) external returns (bytes memory);
12
+ }
13
+
14
+ /// @title PortAllowAssets
15
+ /// @notice Port that permits a list of assets on behalf of a peer host.
16
+ /// Each ASSET block in the request calls `allowAsset`. Restricted to trusted peers.
17
+ abstract contract PortAllowAssets is PortBase, AllowAssetsHook, IPortAllowAssets {
18
+ uint internal immutable portAllowAssetsId = portId(this.portAllowAssets.selector);
19
+
20
+ constructor() {
21
+ emit Port(host, portAllowAssetsId, "1:0", Schemas.Asset, "", false);
22
+ emit Labeled(portAllowAssetsId, bytes32(0), "portAllowAssets");
23
+ }
24
+
25
+ /// @notice Execute the allow-assets peer call.
26
+ /// @param data ASSET block stream supplied by the trusted peer.
27
+ /// @return Empty response bytes.
28
+ function portAllowAssets(bytes calldata data) external onlyPeer returns (bytes memory) {
29
+ (Cur memory assets, , ) = Cursors.init(data, 1);
30
+
31
+ while (assets.i < assets.len) {
32
+ bytes32 asset = assets.unpackAsset();
33
+ allowAsset(asset);
34
+ }
35
+
36
+ assets.complete();
37
+ return "";
38
+ }
39
+ }
40
+
41
+
42
+
43
+
44
+
@@ -0,0 +1,41 @@
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
+ interface IPortAllowance {
11
+ function portAllowance(bytes calldata data) external returns (bytes memory);
12
+ }
13
+
14
+ /// @title PortAllowance
15
+ /// @notice Port that lets a trusted peer host request or refresh its own allowance.
16
+ /// Each AMOUNT block in the request is scoped to the peer host and passed to the
17
+ /// shared allowance hook as a host-scoped allowance. Restricted to trusted peers.
18
+ abstract contract PortAllowance is PortBase, AllowanceHook, IPortAllowance {
19
+ uint internal immutable portAllowanceId = portId(this.portAllowance.selector);
20
+
21
+ constructor() {
22
+ emit Port(host, portAllowanceId, "1:0", Schemas.Amount, "", false);
23
+ emit Labeled(portAllowanceId, bytes32(0), "portAllowance");
24
+ }
25
+
26
+ /// @notice Execute the allowance port call.
27
+ /// @param data AMOUNT block stream requested by the trusted peer.
28
+ /// @return Empty response bytes.
29
+ function portAllowance(bytes calldata data) external onlyPeer returns (bytes memory) {
30
+ (Cur memory amounts, , ) = Cursors.init(data, 1);
31
+ uint peer = caller();
32
+
33
+ while (amounts.i < amounts.len) {
34
+ (bytes32 asset, uint amount) = amounts.unpackAmount();
35
+ allowance(peer, asset, amount);
36
+ }
37
+
38
+ amounts.complete();
39
+ return "";
40
+ }
41
+ }
package/ports/Base.sol ADDED
@@ -0,0 +1,41 @@
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
+ /// @notice ABI-encode a port call from a target port ID and data block stream.
10
+ /// @dev Derives the function selector from `target` via `Nodes.portSelector(target)`.
11
+ /// Reverts if `target` is not a valid port ID.
12
+ /// @param target Destination port node ID embedding the target selector.
13
+ /// @param data Input block stream for the port invocation.
14
+ /// @return ABI-encoded calldata for the port entry point.
15
+ function encodePortCall(uint target, bytes calldata data) pure returns (bytes memory) {
16
+ bytes4 selector = Nodes.portSelector(target);
17
+ return abi.encodeWithSelector(selector, data);
18
+ }
19
+
20
+ /// @title PortBase
21
+ /// @notice Abstract base for peer-facing rootzero ports.
22
+ /// Ports handle inter-host operations between cooperating hosts.
23
+ /// Access is restricted to trusted peer callers via `onlyPeer`.
24
+ abstract contract PortBase is NodeCalls, PortEvent, LabeledEvent {
25
+ /// @dev Thrown when the commander attempts to call a port entrypoint directly.
26
+ error CommanderNotAllowed();
27
+
28
+ /// @dev Restrict execution to trusted callers, excluding the commander.
29
+ modifier onlyPeer() {
30
+ if (msg.sender == commander) revert CommanderNotAllowed();
31
+ enforceCaller(msg.sender);
32
+ _;
33
+ }
34
+
35
+ /// @notice Derive the deterministic node ID for a port selector on this contract.
36
+ /// @param selector Port entrypoint selector.
37
+ /// @return Port node ID.
38
+ function portId(bytes4 selector) internal view returns (uint) {
39
+ return Nodes.toPort(selector, address(this));
40
+ }
41
+ }
@@ -0,0 +1,39 @@
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
+ interface IPortCreditAccount {
11
+ function portCreditAccount(bytes calldata data) external returns (bytes memory);
12
+ }
13
+
14
+ /// @title PortCreditAccount
15
+ /// @notice Port that lets a trusted peer credit supplied accounts directly.
16
+ /// Each ACCOUNT_AMOUNT block calls `creditAccount` for its account.
17
+ abstract contract PortCreditAccount is PortBase, CreditAccountHook, IPortCreditAccount {
18
+ uint internal immutable portCreditAccountId = portId(this.portCreditAccount.selector);
19
+
20
+ constructor() {
21
+ emit Port(host, portCreditAccountId, "1:0", Forms.AccountAmount, "", false);
22
+ emit Labeled(portCreditAccountId, bytes32(0), "portCreditAccount");
23
+ }
24
+
25
+ /// @notice Execute the port-credit call.
26
+ /// @param data ACCOUNT_AMOUNT block stream supplied by the trusted peer.
27
+ /// @return Empty response bytes.
28
+ function portCreditAccount(bytes calldata data) external onlyPeer returns (bytes memory) {
29
+ (Cur memory amounts, , ) = Cursors.init(data, 1);
30
+
31
+ while (amounts.i < amounts.len) {
32
+ (bytes32 account, bytes32 asset, uint amount) = amounts.unpackAccountAmount();
33
+ creditAccount(account, asset, amount);
34
+ }
35
+
36
+ amounts.complete();
37
+ return "";
38
+ }
39
+ }
@@ -0,0 +1,39 @@
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
+ interface IPortDebitAccount {
11
+ function portDebitAccount(bytes calldata data) external returns (bytes memory);
12
+ }
13
+
14
+ /// @title PortDebitAccount
15
+ /// @notice Port that lets a trusted peer debit supplied accounts directly.
16
+ /// Each ACCOUNT_AMOUNT block calls `debitAccount` for its account.
17
+ abstract contract PortDebitAccount is PortBase, DebitAccountHook, IPortDebitAccount {
18
+ uint internal immutable portDebitAccountId = portId(this.portDebitAccount.selector);
19
+
20
+ constructor() {
21
+ emit Port(host, portDebitAccountId, "1:0", Forms.AccountAmount, "", false);
22
+ emit Labeled(portDebitAccountId, bytes32(0), "portDebitAccount");
23
+ }
24
+
25
+ /// @notice Execute the port-debit call.
26
+ /// @param data ACCOUNT_AMOUNT block stream supplied by the trusted peer.
27
+ /// @return Empty response bytes.
28
+ function portDebitAccount(bytes calldata data) external onlyPeer returns (bytes memory) {
29
+ (Cur memory amounts, , ) = Cursors.init(data, 1);
30
+
31
+ while (amounts.i < amounts.len) {
32
+ (bytes32 account, bytes32 asset, uint amount) = amounts.unpackAccountAmount();
33
+ debitAccount(account, asset, amount);
34
+ }
35
+
36
+ amounts.complete();
37
+ return "";
38
+ }
39
+ }