@rootzero/contracts 1.3.0 → 1.5.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 (62) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/Endpoints.sol +4 -3
  3. package/Events.sol +3 -3
  4. package/README.md +58 -31
  5. package/Utils.sol +4 -3
  6. package/blocks/Cursors.sol +83 -143
  7. package/blocks/Keys.sol +15 -15
  8. package/blocks/Schema.sol +27 -28
  9. package/blocks/Writers.sol +26 -33
  10. package/commands/Base.sol +2 -2
  11. package/commands/Burn.sol +3 -4
  12. package/commands/Credit.sol +3 -4
  13. package/commands/Debit.sol +4 -5
  14. package/commands/Deposit.sol +8 -10
  15. package/commands/Payout.sol +3 -6
  16. package/commands/Withdraw.sol +3 -4
  17. package/commands/admin/AllowAssets.sol +5 -6
  18. package/commands/admin/Allowance.sol +3 -4
  19. package/commands/admin/DenyAssets.sol +5 -6
  20. package/commands/admin/Execute.sol +2 -2
  21. package/core/Access.sol +2 -2
  22. package/core/Balances.sol +10 -11
  23. package/core/Calls.sol +7 -7
  24. package/core/Host.sol +2 -2
  25. package/core/Runtime.sol +3 -3
  26. package/core/Types.sol +0 -14
  27. package/docs/Schema.md +29 -10
  28. package/events/Asset.sol +17 -3
  29. package/events/Balance.sol +2 -3
  30. package/events/Commander.sol +19 -0
  31. package/events/Labeled.sol +6 -6
  32. package/events/Locked.sol +2 -3
  33. package/events/Position.sol +2 -3
  34. package/events/Received.sol +2 -3
  35. package/events/Route.sol +18 -0
  36. package/events/Spent.sol +2 -3
  37. package/events/Unlocked.sol +2 -3
  38. package/guards/Base.sol +4 -4
  39. package/package.json +1 -1
  40. package/peer/AllowAssets.sol +3 -3
  41. package/peer/Allowance.sol +2 -2
  42. package/peer/Base.sol +4 -4
  43. package/peer/Credit.sol +10 -10
  44. package/peer/Debit.sol +10 -10
  45. package/peer/DenyAssets.sol +3 -3
  46. package/peer/Recover.sol +51 -0
  47. package/peer/Redeem.sol +48 -0
  48. package/peer/Settle.sol +3 -3
  49. package/queries/Assets.sol +7 -8
  50. package/queries/Balances.sol +8 -9
  51. package/queries/Base.sol +4 -4
  52. package/queries/Positions.sol +4 -6
  53. package/utils/Accounts.sol +76 -58
  54. package/utils/Actions.sol +1 -0
  55. package/utils/Assets.sol +55 -115
  56. package/utils/Ids.sol +33 -233
  57. package/utils/Layout.sol +11 -17
  58. package/utils/Nodes.sol +263 -0
  59. package/utils/Utils.sol +9 -24
  60. package/events/Chain.sol +0 -19
  61. package/events/Transfer.sol +0 -22
  62. package/peer/BalancePull.sol +0 -49
package/CHANGELOG.md CHANGED
@@ -3,6 +3,43 @@
3
3
  Until the protocol reaches integration-stable status, minor versions may include
4
4
  breaking API changes. Breaking changes are called out explicitly.
5
5
 
6
+ ## 1.5.0
7
+
8
+ ### Breaking Changes
9
+
10
+ - Refactored account, asset, and node IDs around the shared convention that
11
+ first byte `0x00` means opaque and nonzero means structured.
12
+ - Replaced the previous generic ID helpers with `Nodes` for structured host,
13
+ chain, command, peer, query, and guard node IDs.
14
+ - Changed assets to single-word IDs: `bytes32 asset` is now the unique asset key
15
+ without a separate metadata field or asset slot.
16
+ - `Payout` now passes destination accounts through to the hook unchanged; payout
17
+ account policy is the hook implementation's responsibility.
18
+
19
+ ### Added
20
+
21
+ - Added `Ids` as the shared helper library for opaque IDs, including
22
+ `Ids.isOpaque`, `Ids.opaque`, `Ids.toKeccak`, and `Ids.matchKeccak`.
23
+ - Added opaque account, asset, and node helper wrappers in `Accounts`, `Assets`,
24
+ and `Nodes`.
25
+ - Added `Asset(uint indexed host, bytes32 asset, bytes preimage)` for declaring
26
+ opaque asset preimages.
27
+ - Documented opaque preimages as `[formatHash:1][payload...]`, where `0x01`
28
+ means keccak256.
29
+
30
+ ## 1.4.0
31
+
32
+ ### Breaking Changes
33
+
34
+ - Replaced the `Chain` discovery event with `Commander(uint indexed host, uint chain, bytes32 native, bytes32 admin)`.
35
+ - Removed the `Transfer` flow event; account flows should be represented with `Spent` and `Received`.
36
+ - Renamed the indexed `Labeled` event parameter from `id` to `entity` in the published ABI string.
37
+
38
+ ### Added
39
+
40
+ - Added `Route(uint indexed host, uint chain, uint context)` for generic cross-chain route discovery.
41
+ - Added `Actions.Refund` for unused payable command value returned through settlement hooks.
42
+
6
43
  ## 1.3.0
7
44
 
8
45
  ### Breaking Changes
package/Endpoints.sol CHANGED
@@ -36,11 +36,12 @@ import { Unauthorize } from "./commands/admin/Unauthorize.sol";
36
36
  import { PeerBase, encodePeerCall } from "./peer/Base.sol";
37
37
  import { PeerAllowAssets, IPeerAllowAssets } from "./peer/AllowAssets.sol";
38
38
  import { PeerAllowance, IPeerAllowance } from "./peer/Allowance.sol";
39
- import { PeerBalancePull, BalancePullHook, IPeerBalancePull } from "./peer/BalancePull.sol";
40
- import { PeerCreditTo, IPeerCreditTo } from "./peer/Credit.sol";
41
- import { PeerDebitFrom, IPeerDebitFrom } from "./peer/Debit.sol";
39
+ import { PeerRedeemBalance, RedeemBalanceHook, IPeerRedeemBalance } from "./peer/Redeem.sol";
40
+ import { PeerCreditAccount, IPeerCreditAccount } from "./peer/Credit.sol";
41
+ import { PeerDebitAccount, IPeerDebitAccount } from "./peer/Debit.sol";
42
42
  import { PeerDenyAssets, IPeerDenyAssets } from "./peer/DenyAssets.sol";
43
43
  import { PeerPipePayable, IPeerPipePayable } from "./peer/Pipe.sol";
44
+ import { PeerRecover, RecoverHook, IPeerRecover } from "./peer/Recover.sol";
44
45
  import { PeerDispatchPayable, IPeerDispatchPayable } from "./peer/Dispatch.sol";
45
46
  import { PeerSettle, IPeerSettle } from "./peer/Settle.sol";
46
47
 
package/Events.sol CHANGED
@@ -5,10 +5,10 @@ pragma solidity ^0.8.33;
5
5
  // Import this file to get access to every event emitter in one import.
6
6
 
7
7
  import { AdminEvent } from "./events/Admin.sol";
8
- import { AssetStatusEvent } from "./events/Asset.sol";
8
+ import { AssetEvent, AssetStatusEvent } from "./events/Asset.sol";
9
9
  import { Actions } from "./utils/Actions.sol";
10
10
  import { BalanceEvent } from "./events/Balance.sol";
11
- import { ChainEvent } from "./events/Chain.sol";
11
+ import { CommanderEvent } from "./events/Commander.sol";
12
12
  import { CommandEvent } from "./events/Command.sol";
13
13
  import { PositionEvent } from "./events/Position.sol";
14
14
  import { ReceivedEvent } from "./events/Received.sol";
@@ -22,8 +22,8 @@ import { NodeEvent } from "./events/Node.sol";
22
22
  import { PeerEvent } from "./events/Peer.sol";
23
23
  import { QueryEvent } from "./events/Query.sol";
24
24
  import { RootedEvent } from "./events/Rooted.sol";
25
+ import { RouteEvent } from "./events/Route.sol";
25
26
  import { SpentEvent } from "./events/Spent.sol";
26
- import { TransferEvent } from "./events/Transfer.sol";
27
27
  import { UnlockedEvent } from "./events/Unlocked.sol";
28
28
 
29
29
 
package/README.md CHANGED
@@ -36,14 +36,13 @@ pragma solidity ^0.8.33;
36
36
 
37
37
  import { Host, Balances } from "@rootzero/contracts/Core.sol";
38
38
  import { Deposit } from "@rootzero/contracts/Endpoints.sol";
39
- import { Assets } from "@rootzero/contracts/Utils.sol";
40
39
 
41
40
  contract ExampleHost is Host, Balances, Deposit {
42
41
  constructor(address rootzero) Host(rootzero) {}
43
42
 
44
- function deposit(bytes32 account, bytes32 asset, bytes32 meta, uint amount) internal override {
45
- uint balance = creditTo(account, Assets.slot(asset, meta), amount);
46
- emit Balance(account, asset, meta, balance, int(amount), depositId);
43
+ function deposit(bytes32 account, bytes32 asset, uint amount) internal override {
44
+ uint balance = creditTo(account, asset, amount);
45
+ emit Balance(account, asset, balance, int(amount), depositId);
47
46
  }
48
47
  }
49
48
  ```
@@ -58,7 +57,7 @@ implementations):
58
57
  const host = await ethers.deployContract("ExampleHost", [deployer.address]);
59
58
 
60
59
  const account = encodeUserAccount(user.address); // receiving account
61
- const request = encodeAmountBlock(asset, meta, 100n); // what to deposit
60
+ const request = encodeAmountBlock(asset, 100n); // what to deposit
62
61
  await host.deposit({ account, state: "0x", request }); // emits Balance
63
62
  ```
64
63
 
@@ -79,10 +78,10 @@ The key is `bytes4(keccak256("#name"))`, and the payload layout is described by
79
78
  a schema string. For example, the block that requests a deposit:
80
79
 
81
80
  ```txt
82
- #amount { bytes32 asset, bytes32 meta, uint amount }
81
+ #amount { bytes32 asset, uint amount }
83
82
  ```
84
83
 
85
- is 104 bytes on the wire: an 8-byte header followed by three big-endian 32-byte
84
+ is 72 bytes on the wire: an 8-byte header followed by two big-endian 32-byte
86
85
  fields. There is no ABI encoding and no chain-specific type anywhere in the
87
86
  format — field types are chain-neutral integers, bytes, and booleans. A deposit
88
87
  request built for an EVM host is byte-for-byte the request a CosmWasm or Solana
@@ -114,8 +113,8 @@ import { concat } from "ethers";
114
113
  import { encodeAmountBlock } from "./helpers/blocks";
115
114
 
116
115
  const request = concat([
117
- encodeAmountBlock(usdc, meta, 250_000_000n),
118
- encodeAmountBlock(dai, meta, 250n * 10n ** 18n),
116
+ encodeAmountBlock(usdc, 250_000_000n),
117
+ encodeAmountBlock(dai, 250n * 10n ** 18n),
119
118
  ]);
120
119
  // deposit(request) returns two #balance blocks, one per #amount
121
120
  ```
@@ -124,29 +123,57 @@ Everything downstream keeps this shape: commands loop over request blocks,
124
123
  settlement loops over transactions, pipelines loop over steps. Batching is
125
124
  never a special case.
126
125
 
127
- ## IDs, Accounts, and Assets
126
+ ## IDs, Accounts, Assets, and Nodes
128
127
 
129
- Everything the protocol touches accounts, assets, chains, hosts, endpoints
130
- is identified by a self-describing 256-bit ID:
128
+ Everything the protocol touches - accounts, assets, chains, hosts, endpoints -
129
+ is identified by one 32-byte word. The first byte selects the convention:
130
+
131
+ - `0x00`: opaque ID, encoded as `0x00 || bytes31(hash)`. The hash preimage is
132
+ not recoverable from the ID; use a lookup table or witness data when the
133
+ native account, asset metadata, or node target is needed.
134
+ - nonzero: structured ID. The value can be deconstructed according to its
135
+ layout.
136
+
137
+ Opaque preimages start with:
138
+
139
+ ```txt
140
+ [uint8 formatHash][payload...]
141
+ ```
142
+
143
+ Only the first byte is protocol-level convention for now. `0x01` means
144
+ keccak256. The remaining payload format is host/domain-specific until a future
145
+ standard defines it.
146
+
147
+ The field supplies the role for opaque IDs: a `bytes32 asset` with first byte
148
+ `0x00` is still an asset, but its native metadata must come from lookup or
149
+ witness data. The Solidity helpers below construct and deconstruct structured
150
+ EVM IDs.
151
+
152
+ Structured EVM IDs use:
131
153
 
132
154
  ```txt
133
155
  [uint32 type][uint32 chainid][192-bit payload]
134
156
  ```
135
157
 
136
- where `type` packs `[vm][width][category][subtype]`. Any ID therefore announces
137
- what it is (an account, an asset, a node) and which chain it lives on, and the
138
- payload usually embeds the underlying address. User accounts are
139
- chain-agnostic; admin and guardian accounts are chain-local. Assets cover the
140
- native coin, ERC-20, ERC-721, and ERC-1155; wide identities carry a second
141
- `meta` word (an ERC-721 token id, for example). Nodes are hosts, commands,
142
- peers, queries, and guards.
158
+ where `type` packs `[uint16 representation][uint8 category][uint8 subtype]`. A
159
+ structured ID announces what it is (an account, an asset, a node) and which
160
+ chain it lives on, and the payload usually embeds the underlying address. User
161
+ accounts are chain-agnostic; admin and guardian accounts are chain-local.
162
+ Assets are unique IDs in the same single-word form as accounts and nodes.
163
+ Nodes are hosts, commands, peers, queries, and guards.
164
+
165
+ Opaque asset declarations use `Asset(host, asset, preimage)`. The preimage
166
+ starts with a one-byte format/hash tag, letting offchain indexers or witnesses
167
+ verify and resolve `0x00 || bytes31(hash(preimage))` assets. `0x01` means
168
+ keccak256.
143
169
 
144
170
  The `Utils.sol` entry point provides the constructors and inspectors:
145
171
 
146
172
  ```solidity
147
173
  bytes32 account = Accounts.toUser(msg.sender); // chain-agnostic user account
148
174
  bytes32 asset = Assets.toErc20(tokenAddress); // ERC-20 asset ID
149
- uint hostId = Ids.toHost(address(this)); // host node ID
175
+ uint hostId = Nodes.toHost(address(this)); // host node ID
176
+ bytes32 opaque = Ids.toKeccak(preimage); // 0x00-prefixed opaque ID
150
177
  ```
151
178
 
152
179
  ## Hosts
@@ -197,9 +224,9 @@ function deposit(CommandContext calldata c) external onlyCommand returns (bytes
197
224
  Writer memory writer = Writers.allocBalances(groups);
198
225
 
199
226
  while (request.i < request.len) {
200
- (bytes32 asset, bytes32 meta, uint amount) = request.unpackAmount();
201
- deposit(c.account, asset, meta, amount); // host policy hook
202
- writer.appendBalance(asset, meta, amount);
227
+ (bytes32 asset, uint amount) = request.unpackAmount();
228
+ deposit(c.account, asset, amount); // host policy hook
229
+ writer.appendBalance(asset, amount);
203
230
  }
204
231
 
205
232
  request.complete();
@@ -270,8 +297,8 @@ standard `getBalances` query takes a run of positions and answers each one in
270
297
  order:
271
298
 
272
299
  ```txt
273
- request: #accountAsset { bytes32 account, bytes32 asset, bytes32 meta }
274
- response: #accountAmount { bytes32 account, bytes32 asset, bytes32 meta, uint amount }
300
+ request: #accountAsset { bytes32 account, bytes32 asset }
301
+ response: #accountAmount { bytes32 account, bytes32 asset, uint amount }
275
302
  ```
276
303
 
277
304
  Like commands, every query announces its request and response schemas at
@@ -283,7 +310,7 @@ Peers are the host-to-host surfaces, callable only by trusted hosts. The two
283
310
  central ones are batches all the way down:
284
311
 
285
312
  - `peerSettle` consumes `#transaction { bytes32 from, bytes32 to, bytes32 asset,
286
- bytes32 meta, uint amount }` blocks, debiting `from` and crediting `to` per
313
+ uint amount }` blocks, debiting `from` and crediting `to` per
287
314
  block — how two hosts record settlement between their ledgers.
288
315
  - `peerPipePayable` consumes `#pipe` blocks, each carrying an account, an
289
316
  initial state, and a run of steps — a complete pipeline delivered by another
@@ -313,8 +340,8 @@ drop a trusted node immediately.
313
340
  Hosts are self-describing. At deployment a host emits the ABI of every event it
314
341
  uses (`EventAbi`), a discovery event per endpoint with its full schemas, and
315
342
  labels for human-readable names. State changes then follow evented
316
- conventions: `Balance` for every ledger change and flow events (`Transfer`,
317
- `Received`, `Spent`) for value movement, each tagged with the endpoint that
343
+ conventions: `Balance` for every ledger change and flow events (`Received`,
344
+ `Spent`, `Locked`, `Unlocked`) for value movement, each tagged with the endpoint that
318
345
  caused it. An indexer can reconstruct the entire repository — endpoints,
319
346
  names, access sets, balances — from logs alone, with no artifact files.
320
347
 
@@ -328,8 +355,8 @@ Import from the package entry points rather than deep paths:
328
355
  mixins and their hooks
329
356
  - `@rootzero/contracts/Cursors.sol` — `Cur` cursor reader, `Writers`, `Schemas`,
330
357
  `Keys`
331
- - `@rootzero/contracts/Utils.sol` — `Ids`, `Assets`, `Accounts`, layout and
332
- value helpers
358
+ - `@rootzero/contracts/Utils.sol` — `Ids`, `Nodes`, `Assets`, `Accounts`,
359
+ layout and value helpers
333
360
  - `@rootzero/contracts/Events.sol` — protocol event contracts
334
361
 
335
362
  Repo layout:
@@ -340,7 +367,7 @@ Repo layout:
340
367
  - `contracts/guards` — guardian direct actions
341
368
  - `contracts/queries` — read-only query endpoints
342
369
  - `contracts/blocks` — block schema, cursor parsing, writers
343
- - `contracts/utils` — IDs, assets, accounts, layout, ECDSA
370
+ - `contracts/utils` — ids, nodes, assets, accounts, layout, ECDSA
344
371
  - `contracts/events` — event contracts and emitters
345
372
  - `docs` — [`Schema.md`](docs/Schema.md) (wire format and schema DSL)
346
373
 
package/Utils.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
- // Aggregator: re-exports all utility libraries (Keys, Accounts, Actions, Assets, ECDSA, Ids, Layout, Utils, Value).
4
+ // Aggregator: re-exports all utility libraries (Keys, Accounts, Actions, Assets, ECDSA, Ids, Nodes, Layout, Utils, Value).
5
5
  // Import this file to access the full utility surface without managing individual paths.
6
6
 
7
7
  import { Keys } from "./blocks/Keys.sol";
@@ -9,10 +9,11 @@ import { Accounts } from "./utils/Accounts.sol";
9
9
  import { Actions } from "./utils/Actions.sol";
10
10
  import { Amounts, Assets } from "./utils/Assets.sol";
11
11
  import { ECDSA } from "./utils/ECDSA.sol";
12
- import { Ids, Selectors } from "./utils/Ids.sol";
12
+ import { Ids } from "./utils/Ids.sol";
13
+ import { Nodes } from "./utils/Nodes.sol";
13
14
  import { Layout } from "./utils/Layout.sol";
14
15
  import { Schemas } from "./blocks/Schema.sol";
15
- import { addrOr, applyBps, beforeBps, bytes32ToInt, bytes32ToString, divisible, hash32, intToBytes32, isFamily, isLocalChain, isLocalFamily, matchesBase, MAX_BPS, max8, max16, max24, max32, max40, max64, max96, max128, max160, NotDivisible, retryTicket, toLocalBase, toLocalFamily, toUnspecifiedBase, ValueOverflow } from "./utils/Utils.sol";
16
+ import { addrOr, applyBps, beforeBps, bytes32ToInt, bytes32ToString, divisible, hash32, intToBytes32, isFamily, matchesBase, MAX_BPS, max8, max16, max24, max32, max40, max64, max96, max128, max160, NotDivisible, retryTicket, toLocalBase, toUnspecifiedBase, ValueOverflow } from "./utils/Utils.sol";
16
17
  import { Budget, Values } from "./utils/Value.sol";
17
18
 
18
19