@rootzero/contracts 0.9.1 → 0.9.3

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/Commands.sol +16 -16
  2. package/Cursors.sol +1 -1
  3. package/Events.sol +3 -3
  4. package/Utils.sol +1 -1
  5. package/blocks/Cursors.sol +51 -9
  6. package/blocks/Keys.sol +1 -1
  7. package/blocks/Schema.sol +98 -4
  8. package/blocks/Writers.sol +21 -125
  9. package/commands/Base.sol +0 -19
  10. package/commands/Burn.sol +2 -2
  11. package/commands/Credit.sol +3 -2
  12. package/commands/Debit.sol +3 -3
  13. package/commands/Deposit.sol +6 -6
  14. package/commands/Pipe.sol +17 -12
  15. package/commands/Provision.sol +8 -8
  16. package/commands/Transfer.sol +2 -2
  17. package/commands/Withdraw.sol +3 -2
  18. package/commands/{control → admin}/AllowAssets.sol +5 -5
  19. package/commands/{control → admin}/Allowance.sol +9 -9
  20. package/commands/{control → admin}/Authorize.sol +5 -5
  21. package/commands/{control → admin}/DenyAssets.sol +5 -5
  22. package/commands/{control → admin}/Destroy.sol +4 -4
  23. package/commands/{control → admin}/Execute.sol +5 -5
  24. package/commands/{control → admin}/Init.sol +4 -4
  25. package/commands/{control → admin}/Unauthorize.sol +5 -5
  26. package/core/Access.sol +2 -2
  27. package/core/Calls.sol +16 -2
  28. package/core/Context.sol +10 -11
  29. package/core/Host.sol +4 -4
  30. package/core/Types.sol +1 -1
  31. package/events/{Control.sol → Admin.sol} +9 -5
  32. package/events/Command.sol +7 -2
  33. package/events/Listing.sol +3 -4
  34. package/events/Peer.sol +26 -0
  35. package/events/{RootZero.sol → Piped.sol} +3 -3
  36. package/events/Query.sol +5 -2
  37. package/package.json +1 -1
  38. package/peer/AllowAssets.sol +38 -0
  39. package/peer/Allowance.sol +35 -0
  40. package/peer/BalancePull.sol +43 -0
  41. package/peer/Base.sol +40 -0
  42. package/peer/DenyAssets.sol +38 -0
  43. package/peer/Settle.sol +32 -0
  44. package/queries/Assets.sol +4 -5
  45. package/queries/Balances.sol +4 -5
  46. package/queries/Positions.sol +4 -5
  47. package/utils/Accounts.sol +8 -0
  48. package/utils/Ids.sol +35 -30
  49. package/utils/Layout.sol +3 -3
  50. package/utils/Utils.sol +2 -2
  51. package/events/Remote.sol +0 -24
  52. package/remote/AllowAssets.sol +0 -39
  53. package/remote/Allowance.sol +0 -36
  54. package/remote/AssetPull.sol +0 -44
  55. package/remote/Base.sol +0 -40
  56. package/remote/DenyAssets.sol +0 -39
  57. package/remote/Settle.sol +0 -33
package/core/Context.sol CHANGED
@@ -27,23 +27,22 @@ abstract contract RootZeroContext {
27
27
  /// @param source Calldata slice to parse.
28
28
  /// @param group Expected block group size (e.g. 1 for single, 2 for paired).
29
29
  /// @return cur Cursor with `bound` set to the end of the first run.
30
- /// @return count Total number of blocks in the run (a multiple of `group`).
31
- /// @return quotient Number of groups in the run (`count / group`).
32
- function cursor(bytes calldata source, uint group) internal pure returns (Cur memory cur, uint count, uint quotient) {
30
+ /// @return groups Number of block groups in the run (`prime block count / group`).
31
+ function cursor(bytes calldata source, uint group) internal pure returns (Cur memory cur, uint groups) {
33
32
  cur = Cursors.open(source);
34
- (, count, quotient) = cur.primeRun(group);
33
+ (, , groups) = cur.primeRun(group);
35
34
  }
36
35
 
37
- /// @notice Open a cursor, prime it, and assert that its normalized quotient matches `expectedQuotient`.
38
- /// Equivalent to `open(source)` followed by `primeRun(group)` and a direct quotient equality check.
39
- /// Reverts with `Cursors.BadRatio` when the quotient does not match.
36
+ /// @notice Open a cursor, prime it, and assert that its group count matches `expectedGroups`.
37
+ /// Equivalent to `open(source)` followed by `primeRun(group)` and a direct group-count equality check.
38
+ /// Reverts with `Cursors.BadRatio` when the group count does not match.
40
39
  /// @param source Calldata slice to parse.
41
40
  /// @param group Expected block group size (e.g. 1 for single, 2 for paired).
42
- /// @param expectedQuotient Required number of groups in the first run.
41
+ /// @param expectedGroups Required number of groups in the first run.
43
42
  /// @return cur Cursor with `bound` set to the end of the first run.
44
- function cursor(bytes calldata source, uint group, uint expectedQuotient) internal pure returns (Cur memory cur) {
43
+ function cursor(bytes calldata source, uint group, uint expectedGroups) internal pure returns (Cur memory cur) {
45
44
  cur = Cursors.open(source);
46
- (, , uint quotient) = cur.primeRun(group);
47
- if (quotient != expectedQuotient) revert Cursors.BadRatio();
45
+ (, , uint groups) = cur.primeRun(group);
46
+ if (groups != expectedGroups) revert Cursors.BadRatio();
48
47
  }
49
48
  }
package/core/Host.sol CHANGED
@@ -2,9 +2,9 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import {AccessControl} from "./Access.sol";
5
- import {Authorize} from "../commands/control/Authorize.sol";
6
- import {Unauthorize} from "../commands/control/Unauthorize.sol";
7
- import {ExecutePayable} from "../commands/control/Execute.sol";
5
+ import {Authorize} from "../commands/admin/Authorize.sol";
6
+ import {Unauthorize} from "../commands/admin/Unauthorize.sol";
7
+ import {ExecutePayable} from "../commands/admin/Execute.sol";
8
8
  import {HostAnnouncedEvent} from "../events/Host.sol";
9
9
  import {IHostDiscovery} from "../interfaces/IHostDiscovery.sol";
10
10
  import {Ids} from "../utils/Ids.sol";
@@ -25,7 +25,7 @@ abstract contract HostDiscovery is HostAnnouncedEvent, IHostDiscovery {
25
25
 
26
26
  /// @title Host
27
27
  /// @notice Abstract base contract for rootzero host implementations.
28
- /// Inherits control command support (authorize, unauthorize, executePayable) and
28
+ /// Inherits admin command support (authorize, unauthorize, executePayable) and
29
29
  /// optionally announces itself to a discovery contract at deployment.
30
30
  /// Accepts native ETH payments via the `receive` function.
31
31
  abstract contract Host is Authorize, Unauthorize, ExecutePayable {
package/core/Types.sol CHANGED
@@ -71,7 +71,7 @@ struct HostAccountAmount {
71
71
  uint amount;
72
72
  }
73
73
 
74
- /// @notice Transfer payload used by transaction blocks and remote settlement.
74
+ /// @notice Transfer payload used by transaction blocks and peer settlement.
75
75
  struct Tx {
76
76
  /// @dev Sender account identifier.
77
77
  bytes32 from;
@@ -4,21 +4,25 @@ pragma solidity ^0.8.33;
4
4
  import { EventEmitter } from "./Emitter.sol";
5
5
 
6
6
  string constant ABI =
7
- "event Control(uint indexed host, uint id, string name, string request, bytes4 state, bytes4 output, bool acceptsValue)";
7
+ "event Admin(uint indexed host, uint id, string name, bytes32 shape, string request, bytes4 state, bytes4 output, bool acceptsValue)";
8
8
 
9
- /// @notice Emitted once per control command during host deployment to publish its request schema and state keys.
10
- abstract contract ControlEvent is EventEmitter {
11
- /// @param host Host node ID that owns this control command.
9
+ /// @notice Emitted once per admin command during host deployment to publish its request schema and state keys.
10
+ abstract contract AdminEvent is EventEmitter {
11
+ /// @param host Host node ID that owns this admin command.
12
12
  /// @param id Command node ID.
13
13
  /// @param name Human-readable command name.
14
+ /// @param shape Per-operation prime block counts encoded as `request:state:output`.
15
+ /// Blocks outside the prime runs are global batch blocks and are excluded
16
+ /// from the counts.
14
17
  /// @param request Schema DSL string describing the request shape.
15
18
  /// @param state Block key expected for input state, or `Keys.Empty`.
16
19
  /// @param output Block key produced for output state, or `Keys.Empty`.
17
20
  /// @param acceptsValue Whether the command entrypoint accepts nonzero `msg.value`.
18
- event Control(
21
+ event Admin(
19
22
  uint indexed host,
20
23
  uint id,
21
24
  string name,
25
+ bytes32 shape,
22
26
  string request,
23
27
  bytes4 state,
24
28
  bytes4 output,
@@ -4,14 +4,18 @@ pragma solidity ^0.8.33;
4
4
  import { EventEmitter } from "./Emitter.sol";
5
5
 
6
6
  string constant ABI =
7
- "event Command(uint indexed host, uint id, string name, string request, bytes4 state, bytes4 output, bool acceptsValue)";
7
+ "event Command(uint indexed host, uint id, string name, bytes32 shape, string request, bytes4 state, bytes4 output, bool acceptsValue)";
8
8
 
9
9
  /// @notice Emitted once per command during host deployment to publish its request schema and state keys.
10
10
  abstract contract CommandEvent is EventEmitter {
11
11
  /// @param host Host node ID that owns this command.
12
12
  /// @param id Command node ID.
13
13
  /// @param name Human-readable command name.
14
- /// @param request Schema DSL string describing the request shape.
14
+ /// @param shape Per-operation prime block counts encoded as `request:state:output`.
15
+ /// Blocks outside the prime runs are global batch blocks and are excluded
16
+ /// from the counts.
17
+ /// @param request Schema DSL string describing the request shape; use `empty;...`
18
+ /// when the request has global blocks but no prime blocks.
15
19
  /// @param state Block key expected for input state, or `Keys.Empty`.
16
20
  /// @param output Block key produced for output state, or `Keys.Empty`.
17
21
  /// @param acceptsValue Whether the command entrypoint accepts nonzero `msg.value`.
@@ -19,6 +23,7 @@ abstract contract CommandEvent is EventEmitter {
19
23
  uint indexed host,
20
24
  uint id,
21
25
  string name,
26
+ bytes32 shape,
22
27
  string request,
23
28
  bytes4 state,
24
29
  bytes4 output,
@@ -3,16 +3,15 @@ pragma solidity ^0.8.33;
3
3
 
4
4
  import { EventEmitter } from "./Emitter.sol";
5
5
 
6
- string constant ABI = "event Listing(uint indexed host, bytes32 asset, bytes32 meta, bool active, bool created)";
6
+ string constant ABI = "event Listing(uint indexed host, bytes32 asset, bytes32 meta, bool active)";
7
7
 
8
- /// @notice Emitted when an asset listing is created or updated on a host.
8
+ /// @notice Emitted when an asset listing is updated on a host.
9
9
  abstract contract ListingEvent is EventEmitter {
10
10
  /// @param host Host node ID that manages this listing.
11
11
  /// @param asset Asset identifier.
12
12
  /// @param meta Asset metadata slot.
13
13
  /// @param active True if the listing is currently active.
14
- /// @param created True if the asset was created as part of this listing.
15
- event Listing(uint indexed host, bytes32 asset, bytes32 meta, bool active, bool created);
14
+ event Listing(uint indexed host, bytes32 asset, bytes32 meta, bool active);
16
15
 
17
16
  constructor() {
18
17
  emit EventAbi(ABI);
@@ -0,0 +1,26 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import { EventEmitter } from "./Emitter.sol";
5
+
6
+ string constant ABI =
7
+ "event Peer(uint indexed host, uint id, string name, bytes32 shape, string request, string response, bool acceptsValue)";
8
+
9
+ /// @notice Emitted once per peer during host deployment to publish its request and response schemas.
10
+ abstract contract PeerEvent is EventEmitter {
11
+ /// @param host Host node ID that owns this peer.
12
+ /// @param id Peer node ID.
13
+ /// @param name Human-readable peer name.
14
+ /// @param shape Prime block counts as `request:response`; global blocks are excluded.
15
+ /// @param request Schema DSL string describing the peer request shape.
16
+ /// @param response Schema DSL string describing the peer response shape.
17
+ /// @param acceptsValue Whether the peer entrypoint accepts nonzero `msg.value`.
18
+ event Peer(uint indexed host, uint id, string name, bytes32 shape, string request, string response, bool acceptsValue);
19
+
20
+ constructor() {
21
+ emit EventAbi(ABI);
22
+ }
23
+ }
24
+
25
+
26
+
@@ -3,14 +3,14 @@ pragma solidity ^0.8.33;
3
3
 
4
4
  import { EventEmitter } from "./Emitter.sol";
5
5
 
6
- string constant ABI = "event RootZero(bytes32 indexed account, uint deadline, uint value)";
6
+ string constant ABI = "event Piped(bytes32 indexed account, uint deadline, uint value)";
7
7
 
8
8
  /// @notice Emitted for root-level protocol actions (e.g. governance or protocol-wide operations).
9
- abstract contract RootZeroEvent is EventEmitter {
9
+ abstract contract PipedEvent is EventEmitter {
10
10
  /// @param account Account identifier associated with the action.
11
11
  /// @param deadline Expiry timestamp of the action.
12
12
  /// @param value Native value associated with the action.
13
- event RootZero(bytes32 indexed account, uint deadline, uint value);
13
+ event Piped(bytes32 indexed account, uint deadline, uint value);
14
14
 
15
15
  constructor() {
16
16
  emit EventAbi(ABI);
package/events/Query.sol CHANGED
@@ -3,16 +3,19 @@ pragma solidity ^0.8.33;
3
3
 
4
4
  import {EventEmitter} from "./Emitter.sol";
5
5
 
6
- string constant ABI = "event Query(uint indexed host, uint id, string name, string request, string response)";
6
+ string constant ABI = "event Query(uint indexed host, uint id, string name, bytes32 shape, string request, string response)";
7
7
 
8
8
  /// @notice Emitted once per query during host deployment to publish its request and response schemas.
9
9
  abstract contract QueryEvent is EventEmitter {
10
10
  /// @param host Host node ID that owns this query.
11
11
  /// @param id Query node ID.
12
12
  /// @param name Human-readable query name.
13
+ /// @param shape Per-operation prime block counts encoded as `request:response`.
14
+ /// Blocks outside the prime runs are global batch blocks and are excluded
15
+ /// from the counts.
13
16
  /// @param request Schema DSL string describing the query request shape.
14
17
  /// @param response Schema DSL string describing the query response shape.
15
- event Query(uint indexed host, uint id, string name, string request, string response);
18
+ event Query(uint indexed host, uint id, string name, bytes32 shape, string request, string response);
16
19
 
17
20
  constructor() {
18
21
  emit EventAbi(ABI);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rootzero/contracts",
3
- "version": "0.9.1",
3
+ "version": "0.9.3",
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,38 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import { PeerBase } 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 PeerAllowAssets
11
+ /// @notice Peer that permits a list of (asset, meta) pairs on behalf of a peer host.
12
+ /// 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);
16
+
17
+ constructor() {
18
+ emit Peer(host, peerAllowAssetsId, NAME, "1:0", Schemas.Asset, "", false);
19
+ }
20
+
21
+ /// @notice Execute the allow-assets peer call.
22
+ function peerAllowAssets(bytes calldata request) external onlyPeer returns (bytes memory) {
23
+ (Cur memory assets, ) = cursor(request, 1);
24
+
25
+ while (assets.i < assets.bound) {
26
+ (bytes32 asset, bytes32 meta) = assets.unpackAsset();
27
+ allowAsset(asset, meta);
28
+ }
29
+
30
+ assets.complete();
31
+ return "";
32
+ }
33
+ }
34
+
35
+
36
+
37
+
38
+
@@ -0,0 +1,35 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import {PeerBase} 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 PeerAllowance
11
+ /// @notice Peer 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 PeerAllowance is PeerBase, AllowanceHook {
15
+ string private constant NAME = "peerAllowance";
16
+ uint internal immutable peerAllowanceId = peerId(NAME);
17
+
18
+ constructor() {
19
+ emit Peer(host, peerAllowanceId, NAME, "1:0", Schemas.Amount, "", false);
20
+ }
21
+
22
+ /// @notice Execute the allowance peer call.
23
+ function peerAllowance(bytes calldata request) external onlyPeer returns (bytes memory) {
24
+ (Cur memory amounts, ) = cursor(request, 1);
25
+ uint peer = caller();
26
+
27
+ while (amounts.i < amounts.bound) {
28
+ (bytes32 asset, bytes32 meta, uint amount) = amounts.unpackAmount();
29
+ allowance(peer, asset, meta, amount);
30
+ }
31
+
32
+ amounts.complete();
33
+ return "";
34
+ }
35
+ }
@@ -0,0 +1,43 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import {PeerBase} from "./Base.sol";
5
+ import {Cursors, Cur, Schemas} from "../Cursors.sol";
6
+
7
+ using Cursors for Cur;
8
+
9
+ abstract contract BalancePullHook {
10
+ /// @notice Override to process one incoming balance-based pull request from a peer host.
11
+ /// @param peer Peer host node ID for this request.
12
+ /// @param asset Requested asset identifier.
13
+ /// @param meta Requested asset metadata slot.
14
+ /// @param amount Requested amount in the asset's native units.
15
+ function balancePull(uint peer, bytes32 asset, bytes32 meta, uint amount) internal virtual;
16
+ }
17
+
18
+ /// @title PeerBalancePull
19
+ /// @notice Peer that pulls requested balances from a peer host into this one.
20
+ /// Each BALANCE block in the request calls `balancePull(peer, asset, meta, amount)`.
21
+ /// Restricted to trusted peers.
22
+ abstract contract PeerBalancePull is PeerBase, BalancePullHook {
23
+ string private constant NAME = "peerBalancePull";
24
+ uint internal immutable peerBalancePullId = peerId(NAME);
25
+
26
+ constructor() {
27
+ emit Peer(host, peerBalancePullId, NAME, "1:0", Schemas.Balance, "", false);
28
+ }
29
+
30
+ /// @notice Execute the balance-pull peer call.
31
+ function peerBalancePull(bytes calldata request) external onlyPeer returns (bytes memory) {
32
+ (Cur memory input, ) = cursor(request, 1);
33
+ uint peer = caller();
34
+
35
+ while (input.i < input.bound) {
36
+ (bytes32 asset, bytes32 meta, uint amount) = input.unpackBalance();
37
+ balancePull(peer, asset, meta, amount);
38
+ }
39
+
40
+ input.complete();
41
+ return "";
42
+ }
43
+ }
package/peer/Base.sol ADDED
@@ -0,0 +1,40 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import { NodeCalls } from "../core/Calls.sol";
5
+ import { PeerEvent } from "../events/Peer.sol";
6
+ import { Ids, Selectors } from "../utils/Ids.sol";
7
+
8
+ /// @notice ABI-encode a peer call from a target peer ID and request block stream.
9
+ /// @dev Derives the function selector from `target` via `Ids.peerSelector(target)`.
10
+ /// Reverts if `target` is not a valid peer ID.
11
+ /// @param target Destination peer node ID embedding the target selector.
12
+ /// @param request Input block stream for the peer invocation.
13
+ /// @return ABI-encoded calldata for the peer entry point.
14
+ function encodePeerCall(uint target, bytes calldata request) pure returns (bytes memory) {
15
+ bytes4 selector = Ids.peerSelector(target);
16
+ return abi.encodeWithSelector(selector, request);
17
+ }
18
+
19
+ /// @title PeerBase
20
+ /// @notice Abstract base for all rootzero peer contracts.
21
+ /// Peers handle inter-host operations and asset allow/deny management
22
+ /// between cooperating hosts. Access is restricted to trusted callers via `onlyPeer`.
23
+ abstract contract PeerBase is NodeCalls, PeerEvent {
24
+ /// @dev Thrown when the commander attempts to call a peer entrypoint directly.
25
+ error CommanderNotAllowed();
26
+
27
+ /// @dev Restrict execution to trusted callers, excluding the commander.
28
+ modifier onlyPeer() {
29
+ if (msg.sender == commander) revert CommanderNotAllowed();
30
+ enforceCaller(msg.sender);
31
+ _;
32
+ }
33
+
34
+ /// @notice Derive the deterministic node ID for a named peer on this contract.
35
+ /// @param name Peer function name (without argument list).
36
+ /// @return Peer node ID.
37
+ function peerId(string memory name) internal view returns (uint) {
38
+ return Ids.toPeer(Selectors.peer(name), address(this));
39
+ }
40
+ }
@@ -0,0 +1,38 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import {PeerBase} 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 PeerDenyAssets
11
+ /// @notice Peer that blocks a list of (asset, meta) pairs on behalf of a peer host.
12
+ /// Each ASSET block in the request calls `denyAsset`. Restricted to trusted peers.
13
+ abstract contract PeerDenyAssets is PeerBase, DenyAssetsHook {
14
+ string private constant NAME = "peerDenyAssets";
15
+ uint internal immutable peerDenyAssetsId = peerId(NAME);
16
+
17
+ constructor() {
18
+ emit Peer(host, peerDenyAssetsId, NAME, "1:0", Schemas.Asset, "", false);
19
+ }
20
+
21
+ /// @notice Execute the deny-assets peer call.
22
+ function peerDenyAssets(bytes calldata request) external onlyPeer returns (bytes memory) {
23
+ (Cur memory assets, ) = cursor(request, 1);
24
+
25
+ while (assets.i < assets.bound) {
26
+ (bytes32 asset, bytes32 meta) = assets.unpackAsset();
27
+ denyAsset(asset, meta);
28
+ }
29
+
30
+ assets.complete();
31
+ return "";
32
+ }
33
+ }
34
+
35
+
36
+
37
+
38
+
@@ -0,0 +1,32 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import { PeerBase } from "./Base.sol";
5
+ import { TransferHook } from "../commands/Transfer.sol";
6
+ import { Cursors, Cur, Schemas } from "../Cursors.sol";
7
+
8
+ using Cursors for Cur;
9
+
10
+ /// @title PeerSettle
11
+ /// @notice Peer that consumes peer-supplied TRANSACTION blocks through the shared transfer hook.
12
+ /// Each TRANSACTION block in the request calls `transfer(value)`. Restricted to trusted peers.
13
+ abstract contract PeerSettle is PeerBase, TransferHook {
14
+ string private constant NAME = "peerSettle";
15
+ uint internal immutable peerSettleId = peerId(NAME);
16
+
17
+ constructor() {
18
+ emit Peer(host, peerSettleId, NAME, "1:0", Schemas.Transaction, "", false);
19
+ }
20
+
21
+ /// @notice Execute the peer-settle call.
22
+ function peerSettle(bytes calldata request) external onlyPeer returns (bytes memory) {
23
+ (Cur memory state, ) = cursor(request, 1);
24
+
25
+ while (state.i < state.bound) {
26
+ transfer(state.unpackTxValue());
27
+ }
28
+
29
+ state.complete();
30
+ return "";
31
+ }
32
+ }
@@ -8,8 +8,6 @@ import {QueryBase} from "./Base.sol";
8
8
  using Cursors for Cur;
9
9
  using Writers for Writer;
10
10
 
11
- string constant NAME = "isAllowedAsset";
12
-
13
11
  abstract contract IsAllowedAssetHook {
14
12
  /// @notice Resolve whether one asset tuple is allowed.
15
13
  /// Concrete implementations define the allowlist policy.
@@ -24,18 +22,19 @@ abstract contract IsAllowedAssetHook {
24
22
  /// The request is a run of `ASSET` blocks.
25
23
  /// The response returns one `STATUS` form block per query entry, preserving request order.
26
24
  abstract contract IsAllowedAsset is QueryBase, IsAllowedAssetHook {
25
+ string private constant NAME = "isAllowedAsset";
27
26
  uint public immutable isAllowedAssetId = queryId(NAME);
28
27
 
29
28
  constructor() {
30
- emit Query(host, isAllowedAssetId, NAME, Schemas.Asset, Forms.Status);
29
+ emit Query(host, isAllowedAssetId, NAME, "1:1", Schemas.Asset, Forms.Status);
31
30
  }
32
31
 
33
32
  /// @notice Resolve allowlist status for a run of requested `(asset, meta)` tuples.
34
33
  /// @param request Block-stream request consisting of `asset(asset, meta)*`.
35
34
  /// @return Block-stream response containing one `status(ok)` per asset block.
36
35
  function isAllowedAsset(bytes calldata request) external view returns (bytes memory) {
37
- (Cur memory query, uint count, ) = cursor(request, 1);
38
- Writer memory response = Writers.allocStatuses(count);
36
+ (Cur memory query, uint groups) = cursor(request, 1);
37
+ Writer memory response = Writers.allocStatuses(groups);
39
38
 
40
39
  while (query.i < query.bound) {
41
40
  (bytes32 asset, bytes32 meta) = query.unpackAsset();
@@ -7,8 +7,6 @@ import {QueryBase} from "./Base.sol";
7
7
  using Cursors for Cur;
8
8
  using Writers for Writer;
9
9
 
10
- string constant NAME = "getBalances";
11
-
12
10
  abstract contract GetBalancesHook {
13
11
  /// @notice Resolve one account's balance for one supported asset.
14
12
  /// Concrete implementations define how assets are resolved.
@@ -24,18 +22,19 @@ abstract contract GetBalancesHook {
24
22
  /// The request is a run of `ACCOUNT_ASSET` form blocks.
25
23
  /// The response returns one `ACCOUNT_AMOUNT` form block per requested position, preserving request order.
26
24
  abstract contract GetBalances is QueryBase, GetBalancesHook {
25
+ string private constant NAME = "getBalances";
27
26
  uint public immutable getBalancesId = queryId(NAME);
28
27
 
29
28
  constructor() {
30
- emit Query(host, getBalancesId, NAME, Forms.AccountAsset, Forms.AccountAmount);
29
+ emit Query(host, getBalancesId, NAME, "1:1", Forms.AccountAsset, Forms.AccountAmount);
31
30
  }
32
31
 
33
32
  /// @notice Resolve balances for a run of requested `(account, asset, meta)` tuples.
34
33
  /// @param request Block-stream request consisting of `accountAsset(account, asset, meta)*`.
35
34
  /// @return Block-stream response containing one `accountAmount(account, asset, meta, amount)` block per request block.
36
35
  function getBalances(bytes calldata request) external view returns (bytes memory) {
37
- (Cur memory query, uint count, ) = cursor(request, 1);
38
- Writer memory response = Writers.allocAccountAmounts(count);
36
+ (Cur memory query, uint groups) = cursor(request, 1);
37
+ Writer memory response = Writers.allocAccountAmounts(groups);
39
38
 
40
39
  while (query.i < query.bound) {
41
40
  (bytes32 account, bytes32 asset, bytes32 meta) = query.unpackAccountAsset();
@@ -7,8 +7,6 @@ import {QueryBase} from "./Base.sol";
7
7
 
8
8
  using Cursors for Cur;
9
9
 
10
- string constant NAME = "getPosition";
11
-
12
10
  abstract contract GetPositionHook {
13
11
  /// @notice Resolve the position payload for one requested position.
14
12
  /// Concrete implementations must append exactly one `RESPONSE` block whose payload
@@ -30,12 +28,13 @@ abstract contract GetPositionHook {
30
28
  /// The request is a run of `ACCOUNT_ASSET` form blocks.
31
29
  /// The response returns one dynamic `RESPONSE` block per position entry, preserving request order.
32
30
  abstract contract GetPosition is QueryBase, GetPositionHook {
31
+ string private constant NAME = "getPosition";
33
32
  uint public immutable getPositionId = queryId(NAME);
34
33
  uint internal immutable positionResponseSize;
35
34
 
36
35
  constructor(string memory output, uint responseSize) {
37
36
  positionResponseSize = responseSize;
38
- emit Query(host, getPositionId, NAME, Forms.AccountAsset, output);
37
+ emit Query(host, getPositionId, NAME, "1:1", Forms.AccountAsset, output);
39
38
  }
40
39
 
41
40
  /// @notice Resolve positions for a run of requested `(account, asset, meta)` tuples.
@@ -44,8 +43,8 @@ abstract contract GetPosition is QueryBase, GetPositionHook {
44
43
  /// @param request Block-stream request consisting of `accountAsset(account, asset, meta)*`.
45
44
  /// @return Block-stream response containing one `response(bytes data)` block per position block.
46
45
  function getPosition(bytes calldata request) external view returns (bytes memory) {
47
- (Cur memory query, uint count, ) = cursor(request, 1);
48
- Writer memory response = Writers.allocBytes(count, positionResponseSize);
46
+ (Cur memory query, uint groups) = cursor(request, 1);
47
+ Writer memory response = Writers.allocBytes(groups, positionResponseSize);
49
48
 
50
49
  while (query.i < query.bound) {
51
50
  (bytes32 account, bytes32 asset, bytes32 meta) = query.unpackAccountAsset();
@@ -92,6 +92,14 @@ library Accounts {
92
92
  return account;
93
93
  }
94
94
 
95
+ /// @notice Assert that `account` is not an admin account and return it unchanged.
96
+ /// @param account Account ID to validate.
97
+ /// @return The same `account` value if valid.
98
+ function ensureNotAdmin(bytes32 account) internal pure returns (bytes32) {
99
+ if (isAdmin(account)) revert InvalidAccount();
100
+ return account;
101
+ }
102
+
95
103
  /// @notice Extract the EVM address embedded in an EVM-family account ID.
96
104
  /// Reverts if `account` is not an EVM-family account.
97
105
  /// @param account EVM-family account ID.