@rootzero/contracts 0.9.2 → 0.9.4

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/Commands.sol +4 -3
  2. package/Core.sol +3 -0
  3. package/Cursors.sol +1 -1
  4. package/README.md +18 -24
  5. package/blocks/Cursors.sol +332 -335
  6. package/blocks/Keys.sol +38 -57
  7. package/blocks/Schema.sol +55 -114
  8. package/blocks/Writers.sol +361 -255
  9. package/commands/Base.sol +6 -48
  10. package/commands/Burn.sol +4 -4
  11. package/commands/Credit.sol +5 -4
  12. package/commands/Debit.sol +6 -5
  13. package/commands/Deposit.sol +17 -14
  14. package/commands/Provision.sol +17 -14
  15. package/commands/Transfer.sol +4 -4
  16. package/commands/Withdraw.sol +5 -4
  17. package/commands/admin/AllowAssets.sol +3 -3
  18. package/commands/admin/Allowance.sol +3 -3
  19. package/commands/admin/Authorize.sol +3 -3
  20. package/commands/admin/DenyAssets.sol +3 -3
  21. package/commands/admin/Destroy.sol +1 -1
  22. package/commands/admin/Execute.sol +9 -8
  23. package/commands/admin/Init.sol +1 -1
  24. package/commands/admin/Unauthorize.sol +3 -3
  25. package/core/Access.sol +11 -0
  26. package/core/Context.sol +11 -13
  27. package/core/Payable.sol +57 -0
  28. package/core/Pipeline.sol +55 -0
  29. package/docs/Schema.md +194 -0
  30. package/events/Admin.sol +5 -1
  31. package/events/Command.sol +6 -2
  32. package/events/Listing.sol +3 -4
  33. package/events/Peer.sol +5 -3
  34. package/events/Query.sol +5 -2
  35. package/package.json +2 -2
  36. package/peer/AllowAssets.sol +3 -3
  37. package/peer/Allowance.sol +3 -3
  38. package/peer/BalancePull.sol +43 -0
  39. package/peer/DenyAssets.sol +3 -3
  40. package/peer/Pipe.sol +38 -0
  41. package/peer/Settle.sol +3 -3
  42. package/queries/Assets.sol +7 -6
  43. package/queries/Balances.sol +5 -4
  44. package/queries/Positions.sol +14 -14
  45. package/utils/Value.sol +8 -14
  46. package/commands/Pipe.sol +0 -67
  47. package/docs/GETTING_STARTED.md +0 -294
  48. package/peer/AssetPull.sol +0 -43
@@ -0,0 +1,57 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import {Budget, Values} from "../utils/Value.sol";
5
+
6
+ /// @title Payable
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.
10
+ abstract contract Payable {
11
+ /// @dev Thrown when a payable entrypoint completes with unspent native value.
12
+ /// Override `settleValue` to implement refund or forwarding behavior instead.
13
+ error UnusedValue(uint remaining);
14
+
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) {
18
+ return Budget({remaining: msg.value});
19
+ }
20
+
21
+ /// @notice Deduct `amount` from the budget and return it.
22
+ /// @param budget Mutable budget to deduct from.
23
+ /// @param amount Native value to spend.
24
+ /// @return The same `amount`, ready to forward to a callee.
25
+ function useValue(Budget memory budget, uint amount) internal pure returns (uint) {
26
+ return Values.use(budget, amount);
27
+ }
28
+
29
+ /// @notice Deduct `amount` from the budget and return it as a new sub-budget.
30
+ /// @param budget Mutable parent budget to deduct from.
31
+ /// @param amount Native value to assign to the sub-budget.
32
+ /// @return A new budget with `amount` remaining.
33
+ function allocateValue(Budget memory budget, uint amount) internal pure returns (Budget memory) {
34
+ return Values.allocate(budget, amount);
35
+ }
36
+
37
+ /// @notice Drains the budget and settles any remaining native value.
38
+ /// @dev Calls the amount-based `settleValue` hook only when some value remains.
39
+ /// @param account Account identifier for the current invocation.
40
+ /// @param budget Mutable native-value budget used during execution.
41
+ function settleValue(bytes32 account, Budget memory budget) internal {
42
+ uint value = budget.remaining;
43
+ if (value == 0) return;
44
+ budget.remaining = 0;
45
+ settleValue(account, value);
46
+ }
47
+
48
+ /// @notice Handles leftover native value after payable execution has finished.
49
+ /// @dev Override this hook to refund or redirect unused value.
50
+ /// The default implementation rejects any leftover amount.
51
+ /// @param account Account identifier for the current invocation.
52
+ /// @param remaining Unspent native value left in the budget, in wei.
53
+ function settleValue(bytes32 account, uint remaining) internal virtual {
54
+ account;
55
+ revert UnusedValue(remaining);
56
+ }
57
+ }
@@ -0,0 +1,55 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import {Cursors, Cur} from "../Cursors.sol";
5
+ import {Payable} from "./Payable.sol";
6
+ import {Budget} from "../utils/Value.sol";
7
+
8
+ using Cursors for Cur;
9
+
10
+ /// @title Pipeline
11
+ /// @notice Core pipeline functionality shared by higher-level surfaces.
12
+ abstract contract Pipeline is Payable {
13
+ error UnexpectedState();
14
+
15
+ /// @notice Override to dispatch one piped step.
16
+ /// Called once per STEP block. The returned bytes become the state passed to
17
+ /// the next step, and the final returned state must be empty.
18
+ /// @param target Node ID to invoke or handle.
19
+ /// @param account Account identifier for the piped context.
20
+ /// @param state Current threaded state block stream.
21
+ /// @param request Step request block stream.
22
+ /// @param value Native value assigned to this step.
23
+ /// @return Updated state block stream for the next step.
24
+ function dispatch(
25
+ uint target,
26
+ bytes32 account,
27
+ bytes memory state,
28
+ bytes calldata request,
29
+ uint value
30
+ ) internal virtual returns (bytes memory);
31
+
32
+ /// @notice Execute a STEP block stream through the pipeline.
33
+ /// @dev Reverts with `UnexpectedState` if the final threaded state is non-empty.
34
+ /// @param account Account identifier used for each dispatched step.
35
+ /// @param state Initial state block stream passed to the first step.
36
+ /// @param steps STEP block stream to execute.
37
+ /// @param budget Mutable native-value budget shared across all steps.
38
+ function pipe(
39
+ bytes32 account,
40
+ bytes memory state,
41
+ bytes calldata steps,
42
+ Budget memory budget
43
+ ) internal {
44
+ (Cur memory input, ) = Cursors.init(steps, 1);
45
+
46
+ while (input.i < input.bound) {
47
+ (uint target, uint value, bytes calldata request) = input.unpackStep();
48
+ state = dispatch(target, account, state, request, useValue(budget, value));
49
+ }
50
+
51
+ if (state.length != 0) revert UnexpectedState();
52
+ settleValue(account, budget);
53
+ input.close();
54
+ }
55
+ }
package/docs/Schema.md ADDED
@@ -0,0 +1,194 @@
1
+ # Schema
2
+
3
+ Rootzero request and response data is encoded as a stream of typed blocks. A
4
+ schema string describes payload layout for discovery events and tooling; the
5
+ runtime block key is derived only from the block name.
6
+
7
+ ## Wire Format
8
+
9
+ Every block uses the same header:
10
+
11
+ ```txt
12
+ [bytes4 key][uint32 payloadLen][payload]
13
+ ```
14
+
15
+ `payloadLen` is big-endian and counts only payload bytes. Child blocks and list
16
+ items use the same header format.
17
+
18
+ The block key is:
19
+
20
+ ```txt
21
+ bytes4(keccak256("#name"))
22
+ ```
23
+
24
+ For example, `#amount { bytes32 asset, bytes32 meta, uint amount }` uses the key
25
+ derived from `#amount`. Blocks must not be overloaded: one block name should have
26
+ one protocol meaning.
27
+
28
+ ## Block Syntax
29
+
30
+ A block starts with `#`. Fixed fields are written in braces:
31
+
32
+ ```txt
33
+ #amount { bytes32 asset, bytes32 meta, uint amount }
34
+ #account { bytes32 account }
35
+ ```
36
+
37
+ A block without braces has no payload:
38
+
39
+ ```txt
40
+ #unit
41
+ #bytes
42
+ ```
43
+
44
+ Empty braces are invalid. A zero-payload block must omit braces.
45
+
46
+ A schema is a comma-separated list of items. Order is significant.
47
+
48
+ ```txt
49
+ #amount { bytes32 asset, bytes32 meta, uint amount },
50
+ maybe #account { bytes32 account }
51
+ ```
52
+
53
+ ## Payload Layout
54
+
55
+ A block payload has fixed fields first, followed by an optional child-block tail.
56
+ Once a child block appears, no more fixed fields may follow.
57
+
58
+ ```txt
59
+ #call { uint target, uint value, #bytes as payload }
60
+ #context { bytes32 account, uint value, #bytes as state, #bytes as request }
61
+ ```
62
+
63
+ The tail is embedded directly as child block bytes. There is no wrapper around a
64
+ child-block tail.
65
+
66
+ Raw dynamic bytes are represented with the reserved `#bytes` child block. Use an
67
+ alias to give those bytes a presentation name:
68
+
69
+ ```txt
70
+ #bytes as payload
71
+ ```
72
+
73
+ ## Modifiers
74
+
75
+ Cardinality is expressed with prefix keywords:
76
+
77
+ ```txt
78
+ #balance { bytes32 asset, bytes32 meta, uint amount }
79
+ maybe #balance { bytes32 asset, bytes32 meta, uint amount }
80
+ many #balance { bytes32 asset, bytes32 meta, uint amount }
81
+ maybe many #balance { bytes32 asset, bytes32 meta, uint amount }
82
+ ```
83
+
84
+ - no prefix: one required item
85
+ - `maybe`: optional item
86
+ - `many`: one `#list` block whose payload contains repeated items
87
+ - `maybe many`: optional `#list` block
88
+
89
+ `maybe` emits no placeholder when absent. `many` wraps repeated items in one
90
+ generic list block; it does not repeat the item in place.
91
+
92
+ ## Prime Items
93
+
94
+ The empty string `""` means no schema. Whitespace-only schemas are invalid.
95
+
96
+ For a non-empty schema, the first top-level item is the prime item. Prime items
97
+ may repeat at the top level for batching. Later top-level items are globals for
98
+ the whole batch and are not counted as per-operation prime blocks.
99
+
100
+ The prime item cannot be optional. If a command needs a per-operation marker with
101
+ no payload, use a zero-payload block such as `#unit`.
102
+
103
+ ## Aliases
104
+
105
+ Aliases are presentation metadata for tooling. They do not change payload layout
106
+ or runtime keys.
107
+
108
+ ```txt
109
+ maybe #account { bytes32 account } as recipient
110
+ #call { uint target, uint value, #bytes as payload }
111
+ ```
112
+
113
+ Aliases may be used on any block item, including child blocks and prime items.
114
+
115
+ ## Field Types
116
+
117
+ Supported field types are chain-neutral:
118
+
119
+ ```txt
120
+ uint, uint8, uint16, uint32, uint64, uint128, uint256
121
+ int, int8, int16, int32, int64, int128, int256
122
+ bool
123
+ bytes1 through bytes32
124
+ ```
125
+
126
+ `uint` means `uint256`; `int` means `int256`. Other integer widths, unsized
127
+ `bytes`, `string`, and array syntax are not part of the core schema DSL.
128
+
129
+ Integers are encoded big-endian. Signed integers use two's-complement encoding
130
+ for their declared width. `bool` is one byte: `0x00` for false and `0x01` for
131
+ true. `bytesN` values are encoded as exactly `N` bytes with no padding.
132
+
133
+ ## Identifiers
134
+
135
+ Block names, field names, and aliases use lower camelCase ASCII identifiers:
136
+
137
+ ```txt
138
+ [a-z][a-zA-Z0-9]*
139
+ ```
140
+
141
+ Invalid examples:
142
+
143
+ ```txt
144
+ Amount
145
+ asset_meta
146
+ asset-meta
147
+ 0account
148
+ ```
149
+
150
+ Reserved words include `maybe`, `many`, `as`, all field type names, and the
151
+ reserved block names `bytes`, `data`, and `list`.
152
+
153
+ ## Reserved Blocks
154
+
155
+ - `#bytes`: raw dynamic bytes, written without a body
156
+ - `#data`: generic/custom payload block
157
+ - `#list`: generic list wrapper emitted by `many`
158
+
159
+ Use `#data` when a local schema needs a stable generic key:
160
+
161
+ ```txt
162
+ #data { uint foo, bytes32 tag }
163
+ #data { #bytes as payload }
164
+ ```
165
+
166
+ If a schema string starts with a fixed field type, it is shorthand for one
167
+ top-level `#data` block:
168
+
169
+ ```txt
170
+ uint foo, bytes32 tag
171
+ ```
172
+
173
+ expands to:
174
+
175
+ ```txt
176
+ #data { uint foo, bytes32 tag }
177
+ ```
178
+
179
+ ## Standard Blocks
180
+
181
+ Common protocol schemas live in `contracts/blocks/Schema.sol`:
182
+
183
+ ```txt
184
+ #amount { bytes32 asset, bytes32 meta, uint amount }
185
+ #balance { bytes32 asset, bytes32 meta, uint amount }
186
+ #custody { uint host, bytes32 asset, bytes32 meta, uint amount }
187
+ #payout { bytes32 account, bytes32 asset, bytes32 meta, uint amount }
188
+ #call { uint target, uint value, #bytes as payload }
189
+ #step { uint target, uint value, #bytes as request }
190
+ #context { bytes32 account, uint value, #bytes as state, #bytes as request }
191
+ #auth { uint cid, uint deadline, #bytes as proof }
192
+ ```
193
+
194
+ `Keys.sol` contains the corresponding runtime keys.
package/events/Admin.sol CHANGED
@@ -4,13 +4,16 @@ pragma solidity ^0.8.33;
4
4
  import { EventEmitter } from "./Emitter.sol";
5
5
 
6
6
  string constant ABI =
7
- "event Admin(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
9
  /// @notice Emitted once per admin command during host deployment to publish its request schema and state keys.
10
10
  abstract contract AdminEvent is EventEmitter {
11
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`.
@@ -19,6 +22,7 @@ abstract contract AdminEvent is EventEmitter {
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,17 @@ 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 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`.
@@ -19,6 +22,7 @@ abstract contract CommandEvent is EventEmitter {
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,
@@ -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);
package/events/Peer.sol CHANGED
@@ -4,16 +4,18 @@ pragma solidity ^0.8.33;
4
4
  import { EventEmitter } from "./Emitter.sol";
5
5
 
6
6
  string constant ABI =
7
- "event Peer(uint indexed host, uint id, string name, string request, bool acceptsValue)";
7
+ "event Peer(uint indexed host, uint id, string name, bytes32 shape, string request, string response, bool acceptsValue)";
8
8
 
9
- /// @notice Emitted once per peer during host deployment to publish its request schema.
9
+ /// @notice Emitted once per peer during host deployment to publish its request and response schemas.
10
10
  abstract contract PeerEvent is EventEmitter {
11
11
  /// @param host Host node ID that owns this peer.
12
12
  /// @param id Peer node ID.
13
13
  /// @param name Human-readable peer name.
14
+ /// @param shape Prime block counts as `request:response`; global blocks are excluded.
14
15
  /// @param request Schema DSL string describing the peer request shape.
16
+ /// @param response Schema DSL string describing the peer response shape.
15
17
  /// @param acceptsValue Whether the peer entrypoint accepts nonzero `msg.value`.
16
- event Peer(uint indexed host, uint id, string name, string request, bool acceptsValue);
18
+ event Peer(uint indexed host, uint id, string name, bytes32 shape, string request, string response, bool acceptsValue);
17
19
 
18
20
  constructor() {
19
21
  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.2",
3
+ "version": "0.9.4",
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",
@@ -9,7 +9,7 @@
9
9
  "**/*.sol",
10
10
  "README.md",
11
11
  "LICENSE",
12
- "docs/GETTING_STARTED.md"
12
+ "docs/Schema.md"
13
13
  ],
14
14
  "publishConfig": {
15
15
  "access": "public"
@@ -15,19 +15,19 @@ abstract contract PeerAllowAssets is PeerBase, AllowAssetsHook {
15
15
  uint internal immutable peerAllowAssetsId = peerId(NAME);
16
16
 
17
17
  constructor() {
18
- emit Peer(host, peerAllowAssetsId, NAME, Schemas.Asset, false);
18
+ emit Peer(host, peerAllowAssetsId, NAME, "1:0", Schemas.Asset, "", false);
19
19
  }
20
20
 
21
21
  /// @notice Execute the allow-assets peer call.
22
22
  function peerAllowAssets(bytes calldata request) external onlyPeer returns (bytes memory) {
23
- (Cur memory assets, , ) = cursor(request, 1);
23
+ (Cur memory assets, ) = cursor(request, 1);
24
24
 
25
25
  while (assets.i < assets.bound) {
26
26
  (bytes32 asset, bytes32 meta) = assets.unpackAsset();
27
27
  allowAsset(asset, meta);
28
28
  }
29
29
 
30
- assets.complete();
30
+ assets.close();
31
31
  return "";
32
32
  }
33
33
  }
@@ -16,12 +16,12 @@ abstract contract PeerAllowance is PeerBase, AllowanceHook {
16
16
  uint internal immutable peerAllowanceId = peerId(NAME);
17
17
 
18
18
  constructor() {
19
- emit Peer(host, peerAllowanceId, NAME, Schemas.Amount, false);
19
+ emit Peer(host, peerAllowanceId, NAME, "1:0", Schemas.Amount, "", false);
20
20
  }
21
21
 
22
22
  /// @notice Execute the allowance peer call.
23
23
  function peerAllowance(bytes calldata request) external onlyPeer returns (bytes memory) {
24
- (Cur memory amounts, , ) = cursor(request, 1);
24
+ (Cur memory amounts, ) = cursor(request, 1);
25
25
  uint peer = caller();
26
26
 
27
27
  while (amounts.i < amounts.bound) {
@@ -29,7 +29,7 @@ abstract contract PeerAllowance is PeerBase, AllowanceHook {
29
29
  allowance(peer, asset, meta, amount);
30
30
  }
31
31
 
32
- amounts.complete();
32
+ amounts.close();
33
33
  return "";
34
34
  }
35
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.close();
41
+ return "";
42
+ }
43
+ }
@@ -15,19 +15,19 @@ abstract contract PeerDenyAssets is PeerBase, DenyAssetsHook {
15
15
  uint internal immutable peerDenyAssetsId = peerId(NAME);
16
16
 
17
17
  constructor() {
18
- emit Peer(host, peerDenyAssetsId, NAME, Schemas.Asset, false);
18
+ emit Peer(host, peerDenyAssetsId, NAME, "1:0", Schemas.Asset, "", false);
19
19
  }
20
20
 
21
21
  /// @notice Execute the deny-assets peer call.
22
22
  function peerDenyAssets(bytes calldata request) external onlyPeer returns (bytes memory) {
23
- (Cur memory assets, , ) = cursor(request, 1);
23
+ (Cur memory assets, ) = cursor(request, 1);
24
24
 
25
25
  while (assets.i < assets.bound) {
26
26
  (bytes32 asset, bytes32 meta) = assets.unpackAsset();
27
27
  denyAsset(asset, meta);
28
28
  }
29
29
 
30
- assets.complete();
30
+ assets.close();
31
31
  return "";
32
32
  }
33
33
  }
package/peer/Pipe.sol ADDED
@@ -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 {Pipeline} from "../core/Pipeline.sol";
6
+ import {Cursors, Cur, Schemas} from "../Cursors.sol";
7
+ import {Budget} from "../utils/Value.sol";
8
+
9
+ using Cursors for Cur;
10
+
11
+ /// @title PeerPipePayable
12
+ /// @notice Peer that consumes CONTEXT blocks and executes each context request as a STEP stream.
13
+ /// Each CONTEXT block carries an account, state, and request; the request is passed to the
14
+ /// shared pipeline as the step stream.
15
+ abstract contract PeerPipePayable is PeerBase, Pipeline {
16
+ string private constant NAME = "peerPipePayable";
17
+ uint internal immutable peerPipePayableId = peerId(NAME);
18
+
19
+ constructor() {
20
+ emit Peer(host, peerPipePayableId, NAME, "1:0", Schemas.Context, "", true);
21
+ }
22
+
23
+ /// @notice Execute peer-supplied contexts through the shared payable pipe.
24
+ /// @dev Each context receives its own explicit value sub-budget. Any top-level
25
+ /// `msg.value` not assigned to a context remains on this host.
26
+ function peerPipePayable(bytes calldata request) external payable onlyPeer returns (bytes memory) {
27
+ (Cur memory input, ) = cursor(request, 1);
28
+ Budget memory budget = valueBudget();
29
+
30
+ while (input.i < input.bound) {
31
+ (bytes32 account, uint value, bytes calldata state, bytes calldata steps) = input.unpackContext();
32
+ pipe(account, state, steps, allocateValue(budget, value));
33
+ }
34
+
35
+ input.close();
36
+ return "";
37
+ }
38
+ }
package/peer/Settle.sol CHANGED
@@ -15,18 +15,18 @@ abstract contract PeerSettle is PeerBase, TransferHook {
15
15
  uint internal immutable peerSettleId = peerId(NAME);
16
16
 
17
17
  constructor() {
18
- emit Peer(host, peerSettleId, NAME, Schemas.Transaction, false);
18
+ emit Peer(host, peerSettleId, NAME, "1:0", Schemas.Transaction, "", false);
19
19
  }
20
20
 
21
21
  /// @notice Execute the peer-settle call.
22
22
  function peerSettle(bytes calldata request) external onlyPeer returns (bytes memory) {
23
- (Cur memory state, , ) = cursor(request, 1);
23
+ (Cur memory state, ) = cursor(request, 1);
24
24
 
25
25
  while (state.i < state.bound) {
26
26
  transfer(state.unpackTxValue());
27
27
  }
28
28
 
29
- state.complete();
29
+ state.close();
30
30
  return "";
31
31
  }
32
32
  }
@@ -26,15 +26,15 @@ abstract contract IsAllowedAsset is QueryBase, IsAllowedAssetHook {
26
26
  uint public immutable isAllowedAssetId = queryId(NAME);
27
27
 
28
28
  constructor() {
29
- emit Query(host, isAllowedAssetId, NAME, Schemas.Asset, Forms.Status);
29
+ emit Query(host, isAllowedAssetId, NAME, "1:1", Schemas.Asset, Forms.Status);
30
30
  }
31
31
 
32
32
  /// @notice Resolve allowlist status for a run of requested `(asset, meta)` tuples.
33
- /// @param request Block-stream request consisting of `asset(asset, meta)*`.
34
- /// @return Block-stream response containing one `status(ok)` per asset block.
33
+ /// @param request Block-stream request consisting of `#asset { bytes32 asset, bytes32 meta }` blocks.
34
+ /// @return Block-stream response containing one `#status { bool ok }` per asset block.
35
35
  function isAllowedAsset(bytes calldata request) external view returns (bytes memory) {
36
- (Cur memory query, uint count, ) = cursor(request, 1);
37
- Writer memory response = Writers.allocStatuses(count);
36
+ (Cur memory query, uint groups) = cursor(request, 1);
37
+ Writer memory response = Writers.allocStatuses(groups);
38
38
 
39
39
  while (query.i < query.bound) {
40
40
  (bytes32 asset, bytes32 meta) = query.unpackAsset();
@@ -42,6 +42,7 @@ abstract contract IsAllowedAsset is QueryBase, IsAllowedAssetHook {
42
42
  response.appendStatus(allowed);
43
43
  }
44
44
 
45
- return query.complete(response);
45
+ query.close();
46
+ return response.finish();
46
47
  }
47
48
  }