@rootzero/contracts 0.7.2 → 0.9.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 (71) hide show
  1. package/Commands.sol +15 -20
  2. package/Core.sol +3 -4
  3. package/Cursors.sol +3 -2
  4. package/Events.sol +1 -1
  5. package/Queries.sol +3 -3
  6. package/README.md +18 -19
  7. package/Utils.sol +3 -3
  8. package/blocks/Cursors.sol +937 -551
  9. package/blocks/Keys.sol +60 -34
  10. package/blocks/Schema.sol +112 -122
  11. package/blocks/Writers.sol +476 -301
  12. package/commands/Base.sol +32 -22
  13. package/commands/Burn.sol +14 -12
  14. package/commands/Credit.sol +16 -15
  15. package/commands/Debit.sol +14 -12
  16. package/commands/Deposit.sol +30 -37
  17. package/commands/Pipe.sol +14 -20
  18. package/commands/Provision.sol +19 -49
  19. package/commands/Transfer.sol +9 -18
  20. package/commands/Withdraw.sol +15 -14
  21. package/commands/admin/AllowAssets.sol +3 -3
  22. package/commands/admin/Allowance.sol +43 -0
  23. package/commands/admin/Authorize.sol +4 -4
  24. package/commands/admin/DenyAssets.sol +3 -3
  25. package/commands/admin/Destroy.sol +10 -8
  26. package/commands/admin/Execute.sol +38 -0
  27. package/commands/admin/Init.sol +10 -8
  28. package/commands/admin/Relocate.sol +5 -5
  29. package/commands/admin/Unauthorize.sol +4 -4
  30. package/core/Access.sol +38 -34
  31. package/core/Balances.sol +17 -18
  32. package/core/{Operation.sol → Calls.sol} +5 -8
  33. package/core/{CursorBase.sol → Context.sol} +11 -5
  34. package/core/Host.sol +11 -10
  35. package/core/Types.sol +86 -0
  36. package/docs/GETTING_STARTED.md +37 -29
  37. package/events/Asset.sol +1 -1
  38. package/events/Command.sol +10 -10
  39. package/events/Deposit.sol +3 -4
  40. package/events/Listing.sol +1 -1
  41. package/events/Peer.sol +3 -3
  42. package/events/Position.sol +21 -0
  43. package/events/Query.sol +3 -3
  44. package/events/Withdraw.sol +2 -3
  45. package/package.json +1 -1
  46. package/peer/AllowAssets.sol +1 -1
  47. package/peer/Allowance.sol +36 -0
  48. package/peer/AssetPull.sol +33 -31
  49. package/peer/Base.sol +8 -4
  50. package/peer/DenyAssets.sol +1 -1
  51. package/peer/Settle.sol +3 -4
  52. package/queries/Assets.sol +18 -16
  53. package/queries/Balances.sol +21 -19
  54. package/queries/Base.sol +2 -3
  55. package/queries/Positions.sol +32 -24
  56. package/utils/Accounts.sol +14 -13
  57. package/utils/Assets.sol +137 -62
  58. package/utils/Ids.sol +9 -9
  59. package/utils/Layout.sol +5 -3
  60. package/utils/Utils.sol +10 -0
  61. package/commands/Create.sol +0 -42
  62. package/commands/Remove.sol +0 -42
  63. package/commands/Settle.sol +0 -38
  64. package/commands/Stake.sol +0 -47
  65. package/commands/Supply.sol +0 -41
  66. package/commands/admin/Allocate.sol +0 -41
  67. package/core/HostBound.sol +0 -14
  68. package/events/Erc721.sol +0 -20
  69. package/peer/Pull.sol +0 -39
  70. package/peer/Push.sol +0 -45
  71. package/utils/State.sol +0 -22
package/core/Types.sol ADDED
@@ -0,0 +1,86 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ /// @notice Asset and amount pair used across ledger, command, and block flows.
5
+ struct AssetAmount {
6
+ /// @dev Asset identifier.
7
+ bytes32 asset;
8
+ /// @dev Asset metadata slot.
9
+ bytes32 meta;
10
+ /// @dev Token amount in the asset's native units.
11
+ uint amount;
12
+ }
13
+
14
+ /// @notice Account-scoped asset shape.
15
+ struct AccountAsset {
16
+ /// @dev Account identifier.
17
+ bytes32 account;
18
+ /// @dev Asset identifier.
19
+ bytes32 asset;
20
+ /// @dev Asset metadata slot.
21
+ bytes32 meta;
22
+ }
23
+
24
+ /// @notice Account-scoped amount shape used by payout blocks and query responses.
25
+ struct AccountAmount {
26
+ /// @dev Account identifier.
27
+ bytes32 account;
28
+ /// @dev Asset identifier.
29
+ bytes32 asset;
30
+ /// @dev Asset metadata slot.
31
+ bytes32 meta;
32
+ /// @dev Token amount in the asset's native units.
33
+ uint amount;
34
+ }
35
+
36
+ /// @notice Host-scoped asset and amount shape.
37
+ struct HostAmount {
38
+ /// @dev Host node identifier.
39
+ uint host;
40
+ /// @dev Asset identifier.
41
+ bytes32 asset;
42
+ /// @dev Asset metadata slot.
43
+ bytes32 meta;
44
+ /// @dev Token amount in the asset's native units.
45
+ uint amount;
46
+ }
47
+
48
+ /// @notice Host-scoped account asset shape.
49
+ struct HostAccountAsset {
50
+ /// @dev Host node identifier.
51
+ uint host;
52
+ /// @dev Account identifier.
53
+ bytes32 account;
54
+ /// @dev Asset identifier.
55
+ bytes32 asset;
56
+ /// @dev Asset metadata slot.
57
+ bytes32 meta;
58
+ }
59
+
60
+ /// @notice Host-scoped account amount shape.
61
+ struct HostAccountAmount {
62
+ /// @dev Host node identifier.
63
+ uint host;
64
+ /// @dev Account identifier.
65
+ bytes32 account;
66
+ /// @dev Asset identifier.
67
+ bytes32 asset;
68
+ /// @dev Asset metadata slot.
69
+ bytes32 meta;
70
+ /// @dev Token amount in the asset's native units.
71
+ uint amount;
72
+ }
73
+
74
+ /// @notice Transfer payload used by transaction blocks and peer settlement.
75
+ struct Tx {
76
+ /// @dev Sender account identifier.
77
+ bytes32 from;
78
+ /// @dev Destination account identifier.
79
+ bytes32 to;
80
+ /// @dev Asset identifier.
81
+ bytes32 asset;
82
+ /// @dev Asset metadata slot.
83
+ bytes32 meta;
84
+ /// @dev Transfer amount in the asset's native units.
85
+ uint amount;
86
+ }
@@ -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
 
@@ -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() {
package/events/Peer.sol CHANGED
@@ -4,16 +4,16 @@ 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, string name, string schema, uint pid, bool acceptsValue)";
7
+ "event Peer(uint indexed host, uint id, string name, string schema, bool acceptsValue)";
8
8
 
9
9
  /// @notice Emitted once per peer during host deployment to publish its schema.
10
10
  abstract contract PeerEvent is EventEmitter {
11
11
  /// @param host Host node ID that owns this peer.
12
+ /// @param id Peer node ID.
12
13
  /// @param name Human-readable peer name.
13
14
  /// @param schema Schema DSL string describing the peer request shape.
14
- /// @param pid Peer node ID.
15
15
  /// @param acceptsValue Whether the peer entrypoint accepts nonzero `msg.value`.
16
- event Peer(uint indexed host, string name, string schema, uint pid, bool acceptsValue);
16
+ event Peer(uint indexed host, uint id, string name, string schema, bool acceptsValue);
17
17
 
18
18
  constructor() {
19
19
  emit EventAbi(ABI);
@@ -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 input, string output)";
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
13
  /// @param input Schema DSL string describing the query request shape.
13
14
  /// @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);
15
+ event Query(uint indexed host, uint id, string name, string input, string output);
16
16
 
17
17
  constructor() {
18
18
  emit EventAbi(ABI);
@@ -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.7.2",
3
+ "version": "0.9.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",
@@ -16,7 +16,7 @@ abstract contract PeerAllowAssets is PeerBase, AllowAssetsHook {
16
16
  uint internal immutable peerAllowAssetsId = peerId(NAME);
17
17
 
18
18
  constructor() {
19
- emit Peer(host, NAME, Schemas.Asset, peerAllowAssetsId, false);
19
+ emit Peer(host, peerAllowAssetsId, NAME, Schemas.Asset, false);
20
20
  }
21
21
 
22
22
  /// @notice Execute the allow-assets peer call.
@@ -0,0 +1,36 @@
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
+ string constant NAME = "peerAllowance";
11
+
12
+ /// @title PeerAllowance
13
+ /// @notice Peer that lets a trusted remote host request or refresh its own allowance.
14
+ /// Each AMOUNT block in the request is scoped to `caller()` and passed to the shared
15
+ /// allowance hook as a host-scoped allowance. Restricted to trusted peers.
16
+ abstract contract PeerAllowance is PeerBase, AllowanceHook {
17
+ uint internal immutable peerAllowanceId = peerId(NAME);
18
+
19
+ constructor() {
20
+ emit Peer(host, peerAllowanceId, NAME, Schemas.Amount, false);
21
+ }
22
+
23
+ /// @notice Execute the allowance peer call.
24
+ function peerAllowance(bytes calldata request) external onlyPeer returns (bytes memory) {
25
+ (Cur memory amounts, , ) = cursor(request, 1);
26
+ uint peer = caller();
27
+
28
+ while (amounts.i < amounts.bound) {
29
+ (bytes32 asset, bytes32 meta, uint amount) = amounts.unpackAmount();
30
+ allowance(peer, asset, meta, amount);
31
+ }
32
+
33
+ amounts.complete();
34
+ return "";
35
+ }
36
+ }
@@ -8,35 +8,37 @@ string constant NAME = "peerAssetPull";
8
8
 
9
9
  using Cursors for Cur;
10
10
 
11
+ abstract contract PeerAssetPullHook {
12
+ /// @notice Override to process one incoming amount-based asset pull request from a remote host.
13
+ /// @param peer Host node ID derived from the caller address.
14
+ /// @param asset Requested asset identifier.
15
+ /// @param meta Requested asset metadata slot.
16
+ /// @param amount Requested amount in the asset's native units.
17
+ function peerAssetPull(uint peer, bytes32 asset, bytes32 meta, uint amount) internal virtual;
18
+ }
19
+
11
20
  /// @title PeerAssetPull
12
- /// @notice Peer that pulls requested asset amounts from a remote host into this one.
13
- /// Each AMOUNT block in the request calls `peerAssetPull(peer, asset, meta, amount)`, where `peer`
14
- /// is derived from `msg.sender`. Restricted to trusted peers.
15
- abstract contract PeerAssetPull is PeerBase {
16
- uint internal immutable peerAssetPullId = peerId(NAME);
17
-
18
- constructor() {
19
- emit Peer(host, NAME, Schemas.Amount, peerAssetPullId, false);
20
- }
21
-
22
- /// @notice Override to process one incoming amount-based asset pull request from a remote host.
23
- /// @param peer Host node ID derived from the caller address.
24
- /// @param asset Requested asset identifier.
25
- /// @param meta Requested asset metadata slot.
26
- /// @param amount Requested amount in the asset's native units.
27
- function peerAssetPull(uint peer, bytes32 asset, bytes32 meta, uint amount) internal virtual;
28
-
29
- /// @notice Execute the asset-pull peer call.
30
- function peerAssetPull(bytes calldata request) external onlyPeer returns (bytes memory) {
31
- (Cur memory assets, , ) = cursor(request, 1);
32
- uint peer = caller();
33
-
34
- while (assets.i < assets.bound) {
35
- (bytes32 asset, bytes32 meta, uint amount) = assets.unpackAmount();
36
- peerAssetPull(peer, asset, meta, amount);
37
- }
38
-
39
- assets.complete();
40
- return "";
41
- }
42
- }
21
+ /// @notice Peer that pulls requested asset amounts from a remote host into this one.
22
+ /// Each AMOUNT block in the request calls `peerAssetPull(peer, asset, meta, amount)`, where `peer`
23
+ /// is derived from `msg.sender`. Restricted to trusted peers.
24
+ abstract contract PeerAssetPull is PeerBase, PeerAssetPullHook {
25
+ uint internal immutable peerAssetPullId = peerId(NAME);
26
+
27
+ constructor() {
28
+ emit Peer(host, peerAssetPullId, NAME, Schemas.Amount, false);
29
+ }
30
+
31
+ /// @notice Execute the asset-pull peer call.
32
+ function peerAssetPull(bytes calldata request) external onlyPeer returns (bytes memory) {
33
+ (Cur memory assets, , ) = cursor(request, 1);
34
+ uint peer = caller();
35
+
36
+ while (assets.i < assets.bound) {
37
+ (bytes32 asset, bytes32 meta, uint amount) = assets.unpackAmount();
38
+ peerAssetPull(peer, asset, meta, amount);
39
+ }
40
+
41
+ assets.complete();
42
+ return "";
43
+ }
44
+ }
package/peer/Base.sol CHANGED
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import { OperationBase } from "../core/Operation.sol";
4
+ import { NodeCalls } from "../core/Calls.sol";
5
5
  import { PeerEvent } from "../events/Peer.sol";
6
6
  import { Ids, Selectors } from "../utils/Ids.sol";
7
7
 
@@ -18,11 +18,15 @@ function encodePeerCall(uint target, bytes calldata request) pure returns (bytes
18
18
 
19
19
  /// @title PeerBase
20
20
  /// @notice Abstract base for all rootzero peer contracts.
21
- /// Peers handle inter-host asset flows (push/pull) and asset allow/deny management
21
+ /// Peers handle inter-host operations and asset allow/deny management
22
22
  /// between cooperating hosts. Access is restricted to trusted callers via `onlyPeer`.
23
- abstract contract PeerBase is OperationBase, PeerEvent {
24
- /// @dev Restrict execution to trusted callers (authorized hosts or the commander).
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.
25
28
  modifier onlyPeer() {
29
+ if (msg.sender == commander) revert CommanderNotAllowed();
26
30
  enforceCaller(msg.sender);
27
31
  _;
28
32
  }
@@ -16,7 +16,7 @@ abstract contract PeerDenyAssets is PeerBase, DenyAssetsHook {
16
16
  uint internal immutable peerDenyAssetsId = peerId(NAME);
17
17
 
18
18
  constructor() {
19
- emit Peer(host, NAME, Schemas.Asset, peerDenyAssetsId, false);
19
+ emit Peer(host, peerDenyAssetsId, NAME, Schemas.Asset, false);
20
20
  }
21
21
 
22
22
  /// @notice Execute the deny-assets peer call.
package/peer/Settle.sol CHANGED
@@ -3,7 +3,7 @@ pragma solidity ^0.8.33;
3
3
 
4
4
  import { PeerBase } from "./Base.sol";
5
5
  import { TransferHook } from "../commands/Transfer.sol";
6
- import { Cursors, Cur, Tx, Schemas } from "../Cursors.sol";
6
+ import { Cursors, Cur, Schemas } from "../Cursors.sol";
7
7
 
8
8
  using Cursors for Cur;
9
9
 
@@ -16,7 +16,7 @@ abstract contract PeerSettle is PeerBase, TransferHook {
16
16
  uint internal immutable peerSettleId = peerId(NAME);
17
17
 
18
18
  constructor() {
19
- emit Peer(host, NAME, Schemas.Transaction, peerSettleId, false);
19
+ emit Peer(host, peerSettleId, NAME, Schemas.Transaction, false);
20
20
  }
21
21
 
22
22
  /// @notice Execute the peer-settle call.
@@ -24,8 +24,7 @@ abstract contract PeerSettle is PeerBase, TransferHook {
24
24
  (Cur memory state, , ) = cursor(request, 1);
25
25
 
26
26
  while (state.i < state.bound) {
27
- Tx memory value = state.unpackTxValue();
28
- transfer(value);
27
+ transfer(state.unpackTxValue());
29
28
  }
30
29
 
31
30
  state.complete();