@rootzero/contracts 0.8.0 → 0.9.1

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 (82) hide show
  1. package/Commands.sol +17 -23
  2. package/Core.sol +3 -4
  3. package/Cursors.sol +4 -6
  4. package/Events.sol +3 -2
  5. package/Queries.sol +1 -1
  6. package/README.md +18 -19
  7. package/Utils.sol +3 -3
  8. package/blocks/Cursors.sol +1398 -0
  9. package/blocks/Keys.sol +57 -34
  10. package/blocks/Schema.sol +108 -126
  11. package/blocks/Writers.sol +476 -301
  12. package/commands/Base.sol +32 -22
  13. package/commands/Burn.sol +5 -5
  14. package/commands/Credit.sol +8 -8
  15. package/commands/Debit.sol +5 -5
  16. package/commands/Deposit.sol +13 -12
  17. package/commands/Pipe.sol +5 -5
  18. package/commands/Provision.sol +25 -54
  19. package/commands/Transfer.sol +11 -21
  20. package/commands/Withdraw.sol +7 -8
  21. package/commands/{admin → control}/AllowAssets.sol +9 -8
  22. package/commands/control/Allowance.sol +43 -0
  23. package/commands/{admin → control}/Authorize.sol +9 -8
  24. package/commands/{admin → control}/DenyAssets.sol +9 -8
  25. package/commands/{admin → control}/Destroy.sol +8 -7
  26. package/commands/control/Execute.sol +39 -0
  27. package/commands/{admin → control}/Init.sol +8 -7
  28. package/commands/{admin → control}/Unauthorize.sol +9 -8
  29. package/core/Access.sol +40 -36
  30. package/core/Balances.sol +17 -18
  31. package/core/{Operation.sol → Calls.sol} +5 -8
  32. package/core/{CursorBase.sol → Context.sol} +11 -5
  33. package/core/Host.sol +9 -9
  34. package/core/Types.sol +86 -0
  35. package/docs/GETTING_STARTED.md +37 -29
  36. package/events/Asset.sol +1 -1
  37. package/events/Command.sol +10 -10
  38. package/events/Control.sol +31 -0
  39. package/events/Deposit.sol +3 -4
  40. package/events/Listing.sol +1 -1
  41. package/events/Position.sol +21 -0
  42. package/events/Query.sol +5 -5
  43. package/events/Remote.sol +24 -0
  44. package/events/Withdraw.sol +2 -3
  45. package/package.json +1 -1
  46. package/queries/Assets.sol +8 -8
  47. package/queries/Balances.sol +11 -11
  48. package/queries/Base.sol +2 -3
  49. package/queries/Positions.sol +25 -19
  50. package/remote/AllowAssets.sol +39 -0
  51. package/remote/Allowance.sol +36 -0
  52. package/remote/AssetPull.sol +44 -0
  53. package/remote/Base.sol +40 -0
  54. package/remote/DenyAssets.sol +39 -0
  55. package/remote/Settle.sol +33 -0
  56. package/utils/Accounts.sol +14 -13
  57. package/utils/Assets.sol +77 -57
  58. package/utils/Ids.sol +34 -34
  59. package/utils/Layout.sol +4 -4
  60. package/utils/Utils.sol +10 -0
  61. package/blocks/cursors/Core.sol +0 -1121
  62. package/blocks/cursors/Erc1155.sol +0 -149
  63. package/blocks/cursors/Erc20.sol +0 -130
  64. package/blocks/cursors/Erc721.sol +0 -66
  65. package/commands/Create.sol +0 -44
  66. package/commands/Remove.sol +0 -44
  67. package/commands/Settle.sol +0 -38
  68. package/commands/Stake.sol +0 -49
  69. package/commands/Supply.sol +0 -43
  70. package/commands/admin/Allocate.sol +0 -43
  71. package/commands/admin/Relocate.sol +0 -43
  72. package/core/HostBound.sol +0 -14
  73. package/events/Erc721.sol +0 -20
  74. package/events/Peer.sol +0 -24
  75. package/peer/AllowAssets.sol +0 -39
  76. package/peer/AssetPull.sol +0 -44
  77. package/peer/Base.sol +0 -36
  78. package/peer/DenyAssets.sol +0 -39
  79. package/peer/Pull.sol +0 -41
  80. package/peer/Push.sol +0 -47
  81. package/peer/Settle.sol +0 -34
  82. package/utils/State.sol +0 -22
@@ -14,7 +14,6 @@ rootzero moves data through a small command context:
14
14
 
15
15
  ```solidity
16
16
  struct CommandContext {
17
- uint target;
18
17
  bytes32 account;
19
18
  bytes state;
20
19
  bytes request;
@@ -25,8 +24,7 @@ In practice:
25
24
 
26
25
  - `account` is the user account the command is acting for.
27
26
  - `request` is the new input for this command.
28
- - `state` is data produced by an earlier command in a pipeline.
29
- - `target` is the command id you expect to receive, or `0`.
27
+ - `state` is live pipeline state produced by an earlier command.
30
28
 
31
29
  Most built-in commands follow a simple pattern:
32
30
 
@@ -86,7 +84,7 @@ contract ExampleHost is Host, DebitAccount {
86
84
  bytes32 meta,
87
85
  uint amount
88
86
  ) internal override {
89
- bytes32 key = Assets.key(asset, meta);
87
+ bytes32 key = Assets.slot(asset, meta);
90
88
  balances[account][key] -= amount;
91
89
  }
92
90
  }
@@ -105,22 +103,21 @@ The built-in commands are easiest to use when you know which blocks they expect.
105
103
  ### Commands That Read `request`
106
104
 
107
105
  - `deposit`: reads `AMOUNT` blocks, returns `BALANCE`
108
- - `transfer`: reads `AMOUNT` plus `RECIPIENT`
106
+ - `transfer`: reads `PAYOUT` blocks
109
107
  - `debitAccount`: reads `AMOUNT`, returns `BALANCE`
110
- - `provision`: reads `AMOUNT` plus `NODE`, returns `CUSTODY`
111
- - `pipe`: reads `STEP` blocks and runs them in order
108
+ - `provision`: reads `ALLOCATION`, returns `CUSTODY` state
109
+ - `pipePayable`: reads `STEP` blocks and runs them in order
112
110
 
113
111
  ### Commands That Read `state`
114
112
 
115
- - `withdraw`: reads `BALANCE`, optionally reads `RECIPIENT` from `request`
116
- - `creditAccount`: reads `BALANCE`, optionally reads `RECIPIENT` from `request`
117
- - `settle`: reads `TX`
118
- - `provisionFromBalance`: reads `BALANCE` from `state` and `NODE` from `request`
113
+ - `withdraw`: reads `BALANCE`, optionally reads `ACCOUNT` from `request`
114
+ - `creditAccount`: reads `BALANCE`, optionally reads `ACCOUNT` from `request`
119
115
 
120
116
  This is the main pattern to keep in mind:
121
117
 
122
118
  - use `request` for the command's direct input
123
- - use `state` when a previous command already produced the input
119
+ - use `state` for live value threaded through a pipeline, currently `BALANCE` and `CUSTODY`
120
+ - use peer commands such as `peerSettle` for settlement messages such as `TRANSACTION`
124
121
 
125
122
  ## Step 4: Send A Simple Request
126
123
 
@@ -137,7 +134,6 @@ const meta = ethers.ZeroHash;
137
134
  const amount = 100n;
138
135
 
139
136
  const ctx = {
140
- target: 0n,
141
137
  account: "0x...", // 32-byte rootzero account id
142
138
  state: "0x",
143
139
  request: encodeAmountBlock(asset, meta, amount),
@@ -160,11 +156,10 @@ When the built-in modules are not enough, add your own command entrypoint.
160
156
  // SPDX-License-Identifier: MIT
161
157
  pragma solidity ^0.8.33;
162
158
 
163
- import {Host} from "@rootzero/contracts/Core.sol";
164
- import {CommandBase, CommandContext, Channels} from "@rootzero/contracts/Commands.sol";
165
- import {Cursors, Cursor, Schemas} from "@rootzero/contracts/Cursors.sol";
159
+ import {CommandBase, CommandContext, Keys} from "@rootzero/contracts/Commands.sol";
160
+ import {Cursors, Cur, Schemas} from "@rootzero/contracts/Cursors.sol";
166
161
 
167
- using Cursors for Cursor;
162
+ using Cursors for Cur;
168
163
 
169
164
  string constant NAME = "myCommand";
170
165
  string constant ROUTE = "route(uint foo, uint bar)";
@@ -174,16 +169,20 @@ abstract contract MyCommand is CommandBase {
174
169
  uint internal immutable myCommandId = commandId(NAME);
175
170
 
176
171
  constructor() {
177
- emit Command(host, NAME, INPUT, myCommandId, Channels.Setup, Channels.Balances);
172
+ emit Command(host, myCommandId, NAME, INPUT, Keys.Empty, Keys.Balance, false);
178
173
  }
179
174
 
180
175
  function myCommand(
181
176
  CommandContext calldata c
182
- ) external payable onlyCommand(myCommandId, c.target) returns (bytes memory) {
183
- Cursor memory input = Cursors.openBlock(c.request, 0);
184
- uint foo = input.unpackRouteUint();
177
+ ) external onlyCommand(c.account) returns (bytes memory) {
178
+ Cur memory input = cursor(c.request);
179
+ uint next = input.bundle();
180
+
181
+ bytes calldata route = input.unpackRaw(Keys.Route);
185
182
  (bytes32 asset, bytes32 meta, uint amount) = input.unpackAmount();
186
- foo;
183
+ input.ensure(next);
184
+
185
+ route;
187
186
  return Cursors.toBalanceBlock(asset, meta, amount);
188
187
  }
189
188
  }
@@ -193,7 +192,7 @@ There are three important ideas here:
193
192
 
194
193
  - every custom command gets a deterministic command id
195
194
  - you announce it with the `Command` event
196
- - `onlyCommand(myCommandId, c.target)` ensures the trusted caller hit the right endpoint
195
+ - `onlyCommand(c.account)` ensures the caller is trusted and the calldata account matches `c.account`
197
196
 
198
197
  ## Step 6: Read Input With A Cursor
199
198
 
@@ -205,7 +204,7 @@ If your request contains a bundled input like:
205
204
 
206
205
  your command can:
207
206
 
208
- - open it with `Cursors.openBlock(...)`
207
+ - open it with `cursor(c.request)` or `Cursors.open(...)`
209
208
  - consume the route first
210
209
  - then consume the amount
211
210
  - keep parsing in bundle/member order without indexing helpers
@@ -232,18 +231,27 @@ function buildBalances() internal pure returns (bytes memory) {
232
231
  Writer memory writer = Writers.allocBalances(2);
233
232
  writer.appendBalance(bytes32(uint256(1)), bytes32(0), 50);
234
233
  writer.appendBalance(bytes32(uint256(2)), bytes32(0), 75);
235
- return writer.done();
234
+ return writer.finish();
236
235
  }
237
236
  ```
238
237
 
239
238
  Use this when your command needs to return:
240
239
 
241
240
  - balances
242
- - custodies
243
- - transactions
241
+ - custody state
244
242
 
245
243
  If you are only consuming built-in commands, you often will not need to touch writers directly.
246
244
 
245
+ ## Query Forms
246
+
247
+ Queries use reusable `Forms` as their input and output schema vocabulary. For example, `getBalances` accepts `Forms.AccountAsset` blocks and returns `Forms.AccountAmount` blocks:
248
+
249
+ ```solidity
250
+ emit Query(host, NAME, Forms.AccountAsset, Forms.AccountAmount, getBalancesId);
251
+ ```
252
+
253
+ This keeps query payloads structural: the query name describes what is being asked, while the form describes the fields carried by each block.
254
+
247
255
  ## A Tiny End-To-End Example
248
256
 
249
257
  Imagine you want a host that keeps internal balances and lets rootzero debit them.
@@ -251,7 +259,7 @@ Imagine you want a host that keeps internal balances and lets rootzero debit the
251
259
  1. Deploy a host that inherits `Host` and `DebitAccount`.
252
260
  2. Store balances in your own mapping.
253
261
  3. Implement `debitAccount(account, asset, meta, amount)`.
254
- 4. Send `debitAccountToBalance` a request containing one or more `AMOUNT` blocks.
262
+ 4. Send `debitAccount` a request containing one or more `AMOUNT` blocks.
255
263
  5. rootzero returns `BALANCE` blocks representing the debited amounts.
256
264
 
257
265
  That is already a valid and useful integration.
@@ -272,7 +280,7 @@ If you want to learn by example, these are the best files to read next:
272
280
 
273
281
  - Passing data in `state` when the command expects it in `request`
274
282
  - Forgetting to emit a `Command` event for a custom command
275
- - Using the wrong `target` value for `onlyCommand`
283
+ - Using an admin account with user-only command flows such as `pipePayable`
276
284
  - Trying to parse raw bytes manually when a built-in reader already exists
277
285
  - Starting with a custom command when a built-in module already matches the job
278
286
 
package/events/Asset.sol CHANGED
@@ -8,7 +8,7 @@ string constant ABI = "event Asset(uint indexed host, bytes32 name, uint32 prefi
8
8
  /// @notice Emitted when an asset is registered or updated on a host.
9
9
  abstract contract AssetEvent is EventEmitter {
10
10
  /// @param host Host node ID that registered the asset.
11
- /// @param name Asset identifier.
11
+ /// @param name Asset name encoded as a bytes32 string.
12
12
  /// @param prefix 4-byte type prefix of the asset ID.
13
13
  event Asset(uint indexed host, bytes32 name, uint32 prefix);
14
14
 
@@ -4,24 +4,24 @@ 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, string name, string schema, uint cid, uint8 stateIn, uint8 stateOut, bool acceptsValue)";
7
+ "event Command(uint indexed host, uint id, string name, string request, bytes4 state, bytes4 output, bool acceptsValue)";
8
8
 
9
- /// @notice Emitted once per command during host deployment to publish its schema and state types.
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
+ /// @param id Command node ID.
12
13
  /// @param name Human-readable command name.
13
- /// @param schema Schema DSL string describing the request shape.
14
- /// @param cid Command node ID.
15
- /// @param stateIn State type discriminant for the input state (see `State` library).
16
- /// @param stateOut State type discriminant for the output state.
14
+ /// @param request Schema DSL string describing the request shape.
15
+ /// @param state Block key expected for input state, or `Keys.Empty`.
16
+ /// @param output Block key produced for output state, or `Keys.Empty`.
17
17
  /// @param acceptsValue Whether the command entrypoint accepts nonzero `msg.value`.
18
18
  event Command(
19
19
  uint indexed host,
20
+ uint id,
20
21
  string name,
21
- string schema,
22
- uint cid,
23
- uint8 stateIn,
24
- uint8 stateOut,
22
+ string request,
23
+ bytes4 state,
24
+ bytes4 output,
25
25
  bool acceptsValue
26
26
  );
27
27
 
@@ -0,0 +1,31 @@
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 Control(uint indexed host, uint id, string name, string request, bytes4 state, bytes4 output, bool acceptsValue)";
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.
12
+ /// @param id Command node ID.
13
+ /// @param name Human-readable command name.
14
+ /// @param request Schema DSL string describing the request shape.
15
+ /// @param state Block key expected for input state, or `Keys.Empty`.
16
+ /// @param output Block key produced for output state, or `Keys.Empty`.
17
+ /// @param acceptsValue Whether the command entrypoint accepts nonzero `msg.value`.
18
+ event Control(
19
+ uint indexed host,
20
+ uint id,
21
+ string name,
22
+ string request,
23
+ bytes4 state,
24
+ bytes4 output,
25
+ bool acceptsValue
26
+ );
27
+
28
+ constructor() {
29
+ emit EventAbi(ABI);
30
+ }
31
+ }
@@ -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 Deposit(bytes32 indexed account, bytes32 asset, bytes32 meta, uint amount, uint cid)";
6
+ string constant ABI = "event Deposit(bytes32 indexed account, bytes32 asset, bytes32 meta, uint amount)";
7
7
 
8
8
  /// @notice Emitted when assets are deposited into an account.
9
9
  abstract contract DepositEvent is EventEmitter {
10
- /// @param account Recipient account identifier.
10
+ /// @param account Destination account identifier.
11
11
  /// @param asset Asset identifier.
12
12
  /// @param meta Asset metadata slot.
13
13
  /// @param amount Amount deposited.
14
- /// @param cid Command ID that triggered the deposit.
15
- event Deposit(bytes32 indexed account, bytes32 asset, bytes32 meta, uint amount, uint cid);
14
+ event Deposit(bytes32 indexed account, bytes32 asset, bytes32 meta, uint amount);
16
15
 
17
16
  constructor() {
18
17
  emit EventAbi(ABI);
@@ -11,7 +11,7 @@ abstract contract ListingEvent is EventEmitter {
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 this is a new listing (false if it is an update).
14
+ /// @param created True if the asset was created as part of this listing.
15
15
  event Listing(uint indexed host, bytes32 asset, bytes32 meta, bool active, bool created);
16
16
 
17
17
  constructor() {
@@ -0,0 +1,21 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import {EventEmitter} from "./Emitter.sol";
5
+
6
+ string constant ABI = "event AssetPosition(bytes32 indexed account, bytes32 asset, bytes32 meta, uint value, uint queryId)";
7
+
8
+ /// @notice Emitted when the reported value of an asset-backed position changes or is observed.
9
+ /// A value of 0 should be interpreted as a closed position.
10
+ abstract contract AssetPositionEvent is EventEmitter {
11
+ /// @param account Account identifier that owns or is associated with the position.
12
+ /// @param asset Asset identifier for the asset class.
13
+ /// @param meta Asset metadata slot carrying the position context.
14
+ /// @param value Context-specific position value; 0 indicates a closed position.
15
+ /// @param queryId Query ID associated with the position lookup or reporting context.
16
+ event AssetPosition(bytes32 indexed account, bytes32 asset, bytes32 meta, uint value, uint queryId);
17
+
18
+ constructor() {
19
+ emit EventAbi(ABI);
20
+ }
21
+ }
package/events/Query.sol CHANGED
@@ -3,16 +3,16 @@ 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, string name, string input, string output, uint qid)";
6
+ string constant ABI = "event Query(uint indexed host, uint id, string name, 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
+ /// @param id Query node ID.
11
12
  /// @param name Human-readable query name.
12
- /// @param input Schema DSL string describing the query request shape.
13
- /// @param output Schema DSL string describing the query response shape.
14
- /// @param qid Query node ID.
15
- event Query(uint indexed host, string name, string input, string output, uint qid);
13
+ /// @param request Schema DSL string describing the query request shape.
14
+ /// @param response Schema DSL string describing the query response shape.
15
+ event Query(uint indexed host, uint id, string name, string request, string response);
16
16
 
17
17
  constructor() {
18
18
  emit EventAbi(ABI);
@@ -0,0 +1,24 @@
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 Remote(uint indexed host, uint id, string name, string request, bool acceptsValue)";
8
+
9
+ /// @notice Emitted once per remote during host deployment to publish its request schema.
10
+ abstract contract RemoteEvent is EventEmitter {
11
+ /// @param host Host node ID that owns this remote.
12
+ /// @param id Remote node ID.
13
+ /// @param name Human-readable remote name.
14
+ /// @param request Schema DSL string describing the remote request shape.
15
+ /// @param acceptsValue Whether the remote entrypoint accepts nonzero `msg.value`.
16
+ event Remote(uint indexed host, uint id, string name, string request, bool acceptsValue);
17
+
18
+ constructor() {
19
+ emit EventAbi(ABI);
20
+ }
21
+ }
22
+
23
+
24
+
@@ -3,7 +3,7 @@ pragma solidity ^0.8.33;
3
3
 
4
4
  import { EventEmitter } from "./Emitter.sol";
5
5
 
6
- string constant ABI = "event Withdrawal(bytes32 indexed account, bytes32 asset, bytes32 meta, uint amount, uint cid)";
6
+ string constant ABI = "event Withdrawal(bytes32 indexed account, bytes32 asset, bytes32 meta, uint amount)";
7
7
 
8
8
  /// @notice Emitted when assets are withdrawn from an account.
9
9
  abstract contract WithdrawalEvent is EventEmitter {
@@ -11,8 +11,7 @@ abstract contract WithdrawalEvent is EventEmitter {
11
11
  /// @param asset Asset identifier.
12
12
  /// @param meta Asset metadata slot.
13
13
  /// @param amount Amount withdrawn.
14
- /// @param cid Command ID that triggered the withdrawal.
15
- event Withdrawal(bytes32 indexed account, bytes32 asset, bytes32 meta, uint amount, uint cid);
14
+ event Withdrawal(bytes32 indexed account, bytes32 asset, bytes32 meta, uint amount);
16
15
 
17
16
  constructor() {
18
17
  emit EventAbi(ABI);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rootzero/contracts",
3
- "version": "0.8.0",
3
+ "version": "0.9.1",
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",
@@ -1,14 +1,14 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import {Cur, Cursors, Writer, Writers, Keys} from "../Cursors.sol";
5
- import {Schemas} from "../blocks/Schema.sol";
4
+ import {Cur, Cursors, Writer, Writers} from "../Cursors.sol";
5
+ import {Forms, Schemas} from "../blocks/Schema.sol";
6
6
  import {QueryBase} from "./Base.sol";
7
7
 
8
8
  using Cursors for Cur;
9
+ using Writers for Writer;
9
10
 
10
11
  string constant NAME = "isAllowedAsset";
11
- string constant OUTPUT = "response(uint allowed)";
12
12
 
13
13
  abstract contract IsAllowedAssetHook {
14
14
  /// @notice Resolve whether one asset tuple is allowed.
@@ -22,25 +22,25 @@ abstract contract IsAllowedAssetHook {
22
22
  /// @title IsAllowedAsset
23
23
  /// @notice Rootzero query that checks whether one or more `(asset, meta)` tuples are allowed.
24
24
  /// The request is a run of `ASSET` blocks.
25
- /// The response returns one `RESPONSE` block per query entry, preserving request order.
25
+ /// The response returns one `STATUS` form block per query entry, preserving request order.
26
26
  abstract contract IsAllowedAsset is QueryBase, IsAllowedAssetHook {
27
27
  uint public immutable isAllowedAssetId = queryId(NAME);
28
28
 
29
29
  constructor() {
30
- emit Query(host, NAME, Schemas.Asset, OUTPUT, isAllowedAssetId);
30
+ emit Query(host, isAllowedAssetId, NAME, Schemas.Asset, Forms.Status);
31
31
  }
32
32
 
33
33
  /// @notice Resolve allowlist status for a run of requested `(asset, meta)` tuples.
34
34
  /// @param request Block-stream request consisting of `asset(asset, meta)*`.
35
- /// @return Block-stream response containing one `response(allowed)` per asset block.
35
+ /// @return Block-stream response containing one `status(ok)` per asset block.
36
36
  function isAllowedAsset(bytes calldata request) external view returns (bytes memory) {
37
37
  (Cur memory query, uint count, ) = cursor(request, 1);
38
- Writer memory response = Writers.alloc32s(count);
38
+ Writer memory response = Writers.allocStatuses(count);
39
39
 
40
40
  while (query.i < query.bound) {
41
41
  (bytes32 asset, bytes32 meta) = query.unpackAsset();
42
42
  bool allowed = isAllowedAsset(asset, meta);
43
- Writers.appendBool(response, Keys.Response, allowed);
43
+ response.appendStatus(allowed);
44
44
  }
45
45
 
46
46
  return query.complete(response);
@@ -1,13 +1,13 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import {Cur, Cursors, Schemas, Writer, Writers} from "../Cursors.sol";
4
+ import {Cur, Cursors, Forms, Writer, Writers} from "../Cursors.sol";
5
5
  import {QueryBase} from "./Base.sol";
6
6
 
7
7
  using Cursors for Cur;
8
+ using Writers for Writer;
8
9
 
9
10
  string constant NAME = "getBalances";
10
- string constant INPUT = "query(bytes32 account, bytes32 asset, bytes32 meta)";
11
11
 
12
12
  abstract contract GetBalancesHook {
13
13
  /// @notice Resolve one account's balance for one supported asset.
@@ -21,26 +21,26 @@ abstract contract GetBalancesHook {
21
21
 
22
22
  /// @title GetBalances
23
23
  /// @notice Rootzero query that resolves balances for one or more `(account, asset, meta)` tuples.
24
- /// The request is a run of `QUERY` blocks, each encoding `(bytes32 account, bytes32 asset, bytes32 meta)`.
25
- /// The response returns one `BALANCE` block per query entry, preserving request order.
24
+ /// The request is a run of `ACCOUNT_ASSET` form blocks.
25
+ /// The response returns one `ACCOUNT_AMOUNT` form block per requested position, preserving request order.
26
26
  abstract contract GetBalances is QueryBase, GetBalancesHook {
27
27
  uint public immutable getBalancesId = queryId(NAME);
28
28
 
29
29
  constructor() {
30
- emit Query(host, NAME, INPUT, Schemas.Balance, getBalancesId);
30
+ emit Query(host, getBalancesId, NAME, Forms.AccountAsset, Forms.AccountAmount);
31
31
  }
32
32
 
33
33
  /// @notice Resolve balances for a run of requested `(account, asset, meta)` tuples.
34
- /// @param request Block-stream request consisting of `query(account, asset, meta)*`.
35
- /// @return Block-stream response containing one `balance(asset, meta, amount)` per query block.
34
+ /// @param request Block-stream request consisting of `accountAsset(account, asset, meta)*`.
35
+ /// @return Block-stream response containing one `accountAmount(account, asset, meta, amount)` block per request block.
36
36
  function getBalances(bytes calldata request) external view returns (bytes memory) {
37
37
  (Cur memory query, uint count, ) = cursor(request, 1);
38
- Writer memory response = Writers.allocBalances(count);
38
+ Writer memory response = Writers.allocAccountAmounts(count);
39
39
 
40
40
  while (query.i < query.bound) {
41
- (bytes32 account, bytes32 asset, bytes32 meta) = query.unpackQuery96();
42
- uint balance = getBalance(account, asset, meta);
43
- Writers.appendBalance(response, asset, meta, balance);
41
+ (bytes32 account, bytes32 asset, bytes32 meta) = query.unpackAccountAsset();
42
+ uint amount = getBalance(account, asset, meta);
43
+ response.appendAccountAmount(account, asset, meta, amount);
44
44
  }
45
45
 
46
46
  return query.complete(response);
package/queries/Base.sol CHANGED
@@ -1,8 +1,7 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import { CursorBase } from "../core/CursorBase.sol";
5
- import { HostBound } from "../core/HostBound.sol";
4
+ import { RootZeroContext } from "../core/Context.sol";
6
5
  import { QueryEvent } from "../events/Query.sol";
7
6
  import { Ids, Selectors } from "../utils/Ids.sol";
8
7
 
@@ -21,7 +20,7 @@ function encodeQueryCall(uint target, bytes calldata request) pure returns (byte
21
20
  /// @notice Abstract base for rootzero query contracts.
22
21
  /// Queries are view-only entry points that consume a block-stream request and
23
22
  /// return a block-stream response.
24
- abstract contract QueryBase is CursorBase, HostBound, QueryEvent {
23
+ abstract contract QueryBase is RootZeroContext, QueryEvent {
25
24
 
26
25
  /// @notice Derive the deterministic node ID for a named query on this contract.
27
26
  /// The ID encodes the ABI selector of `name(bytes)` and `address(this)`,
@@ -1,49 +1,55 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import {Cur, Cursors, Writer, Writers, Keys} from "../Cursors.sol";
5
- import {Schemas} from "../blocks/Schema.sol";
4
+ import {Cur, Cursors, Writer, Writers} from "../Cursors.sol";
5
+ import {Forms} from "../blocks/Schema.sol";
6
6
  import {QueryBase} from "./Base.sol";
7
7
 
8
8
  using Cursors for Cur;
9
9
 
10
- string constant NAME = "getAssetPosition";
10
+ string constant NAME = "getPosition";
11
11
 
12
- abstract contract AssetPositionHook {
13
- /// @notice Resolve the position payload for one requested asset tuple.
12
+ abstract contract GetPositionHook {
13
+ /// @notice Resolve the position payload for one requested position.
14
14
  /// Concrete implementations must append exactly one `RESPONSE` block whose payload
15
15
  /// length matches `positionResponseSize`.
16
+ /// @param account Requested account identifier.
16
17
  /// @param asset Requested asset identifier.
17
18
  /// @param meta Requested asset metadata slot.
18
19
  /// @param response Destination writer for the response stream.
19
- function appendAssetPosition(bytes32 asset, bytes32 meta, Writer memory response) internal view virtual;
20
+ function appendPosition(
21
+ bytes32 account,
22
+ bytes32 asset,
23
+ bytes32 meta,
24
+ Writer memory response
25
+ ) internal view virtual;
20
26
  }
21
27
 
22
- /// @title PositionsQuery
23
- /// @notice Rootzero query that resolves one dynamic position response for each requested asset tuple.
24
- /// The request is a run of `ASSET` blocks.
25
- /// The response returns one dynamic `RESPONSE` block per asset entry, preserving request order.
26
- abstract contract AssetPosition is QueryBase, AssetPositionHook {
27
- uint public immutable getAssetPositionId = queryId(NAME);
28
+ /// @title GetPosition
29
+ /// @notice Rootzero query that resolves one dynamic position response for each requested position.
30
+ /// The request is a run of `ACCOUNT_ASSET` form blocks.
31
+ /// The response returns one dynamic `RESPONSE` block per position entry, preserving request order.
32
+ abstract contract GetPosition is QueryBase, GetPositionHook {
33
+ uint public immutable getPositionId = queryId(NAME);
28
34
  uint internal immutable positionResponseSize;
29
35
 
30
36
  constructor(string memory output, uint responseSize) {
31
37
  positionResponseSize = responseSize;
32
- emit Query(host, NAME, Schemas.Asset, output, getAssetPositionId);
38
+ emit Query(host, getPositionId, NAME, Forms.AccountAsset, output);
33
39
  }
34
40
 
35
- /// @notice Resolve positions for a run of requested `(asset, meta)` tuples.
41
+ /// @notice Resolve positions for a run of requested `(account, asset, meta)` tuples.
36
42
  /// @dev Allocates from the configured fixed response payload length so each hook call
37
43
  /// can append one `RESPONSE` block directly into the output stream.
38
- /// @param request Block-stream request consisting of `asset(asset, meta)*`.
39
- /// @return Block-stream response containing one `response(bytes data)` block per asset block.
40
- function getAssetPosition(bytes calldata request) external view returns (bytes memory) {
44
+ /// @param request Block-stream request consisting of `accountAsset(account, asset, meta)*`.
45
+ /// @return Block-stream response containing one `response(bytes data)` block per position block.
46
+ function getPosition(bytes calldata request) external view returns (bytes memory) {
41
47
  (Cur memory query, uint count, ) = cursor(request, 1);
42
48
  Writer memory response = Writers.allocBytes(count, positionResponseSize);
43
49
 
44
50
  while (query.i < query.bound) {
45
- (bytes32 asset, bytes32 meta) = query.unpackAsset();
46
- appendAssetPosition(asset, meta, response);
51
+ (bytes32 account, bytes32 asset, bytes32 meta) = query.unpackAccountAsset();
52
+ appendPosition(account, asset, meta, response);
47
53
  }
48
54
 
49
55
  return query.complete(response);
@@ -0,0 +1,39 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import { RemoteBase } from "./Base.sol";
5
+ import { AllowAssetsHook } from "../commands/control/AllowAssets.sol";
6
+ import { Cursors, Cur, Schemas } from "../Cursors.sol";
7
+
8
+ using Cursors for Cur;
9
+
10
+ string constant NAME = "remoteAllowAssets";
11
+
12
+ /// @title RemoteAllowAssets
13
+ /// @notice Remote that permits a list of (asset, meta) pairs on behalf of a remote host.
14
+ /// Each ASSET block in the request calls `allowAsset`. Restricted to trusted remotes.
15
+ abstract contract RemoteAllowAssets is RemoteBase, AllowAssetsHook {
16
+ uint internal immutable remoteAllowAssetsId = remoteId(NAME);
17
+
18
+ constructor() {
19
+ emit Remote(host, remoteAllowAssetsId, NAME, Schemas.Asset, false);
20
+ }
21
+
22
+ /// @notice Execute the allow-assets remote call.
23
+ function remoteAllowAssets(bytes calldata request) external onlyRemote returns (bytes memory) {
24
+ (Cur memory assets, , ) = cursor(request, 1);
25
+
26
+ while (assets.i < assets.bound) {
27
+ (bytes32 asset, bytes32 meta) = assets.unpackAsset();
28
+ allowAsset(asset, meta);
29
+ }
30
+
31
+ assets.complete();
32
+ return "";
33
+ }
34
+ }
35
+
36
+
37
+
38
+
39
+