@rootzero/contracts 1.14.0 → 1.16.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 (68) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/Codec.sol +1 -1
  3. package/Commands.sol +1 -1
  4. package/Core.sol +8 -5
  5. package/Endpoints.sol +6 -6
  6. package/Events.sol +1 -3
  7. package/README.md +118 -35
  8. package/annotations/Action.sol +17 -0
  9. package/annotations/Label.sol +23 -0
  10. package/annotations/Schema.sol +53 -0
  11. package/codec/Blocks.sol +127 -32
  12. package/codec/Buffers.sol +3 -3
  13. package/codec/Decoders.sol +46 -13
  14. package/codec/Descriptors.sol +0 -1
  15. package/codec/Keys.sol +8 -4
  16. package/codec/Readers.sol +13 -0
  17. package/codec/Schema.sol +12 -3
  18. package/codec/Specs.sol +28 -4
  19. package/codec/Writers.sol +21 -6
  20. package/commands/Base.sol +39 -44
  21. package/commands/Burn.sol +8 -4
  22. package/commands/Credit.sol +35 -3
  23. package/commands/Debit.sol +46 -13
  24. package/commands/Deposit.sol +14 -8
  25. package/commands/Payout.sol +6 -2
  26. package/commands/Provision.sol +4 -4
  27. package/commands/Recover.sol +15 -8
  28. package/commands/Relay.sol +37 -12
  29. package/commands/Settle.sol +77 -0
  30. package/commands/Withdraw.sol +8 -4
  31. package/commands/admin/AllowAssets.sol +2 -2
  32. package/commands/admin/Allowance.sol +2 -2
  33. package/commands/admin/Annotate.sol +36 -0
  34. package/commands/admin/Appoint.sol +6 -5
  35. package/commands/admin/Authorize.sol +2 -2
  36. package/commands/admin/Base.sol +8 -1
  37. package/commands/admin/DenyAssets.sol +2 -2
  38. package/commands/admin/Dismiss.sol +6 -5
  39. package/commands/admin/Execute.sol +5 -4
  40. package/commands/admin/Unauthorize.sol +2 -2
  41. package/core/Access.sol +91 -49
  42. package/core/Calls.sol +27 -22
  43. package/core/Endpoint.sol +36 -46
  44. package/core/Host.sol +63 -21
  45. package/core/Pipeline.sol +6 -6
  46. package/core/Settlement.sol +37 -8
  47. package/core/Types.sol +13 -1
  48. package/docs/Schema.md +68 -7
  49. package/events/Annotation.sol +24 -0
  50. package/events/Guardian.sol +2 -2
  51. package/events/Introduction.sol +3 -2
  52. package/execution/Budget.sol +12 -4
  53. package/execution/Execution.sol +113 -18
  54. package/guards/Base.sol +4 -4
  55. package/package.json +1 -1
  56. package/ports/Base.sol +19 -7
  57. package/ports/Dispatch.sol +6 -6
  58. package/ports/{Settle.sol → Post.sol} +13 -9
  59. package/queries/Base.sol +18 -3
  60. package/utils/Accounts.sol +0 -23
  61. package/utils/Actions.sol +1 -0
  62. package/utils/Cursors.sol +79 -34
  63. package/utils/Layout.sol +0 -2
  64. package/commands/admin/Label.sol +0 -36
  65. package/commands/admin/Schemas.sol +0 -36
  66. package/events/Labeled.sol +0 -21
  67. package/events/Position.sol +0 -22
  68. package/events/Schema.sol +0 -23
package/codec/Blocks.sol CHANGED
@@ -35,10 +35,6 @@ library Blocks {
35
35
  error UnexpectedValue();
36
36
  /// @dev A scoped block run contained no blocks.
37
37
  error EmptyRun();
38
- /// @dev A block run was scoped with a zero stride.
39
- error ZeroStride();
40
- /// @dev A block count is not divisible by its declared stride.
41
- error BadRatio();
42
38
 
43
39
  // -------------------------------------------------------------------------
44
40
  // Calldata inspection and navigation
@@ -147,25 +143,16 @@ library Blocks {
147
143
  }
148
144
  }
149
145
 
150
- /// @notice Scope a consecutive block run into equal-sized groups.
146
+ /// @notice Count a run that must consume the complete region.
147
+ /// @dev Reverts when any well-formed trailing block has a different key.
151
148
  /// @param abs Absolute start position.
152
149
  /// @param limit Absolute region boundary.
153
- /// @param key Block key forming the run.
154
- /// @param stride Number of blocks per group.
155
- /// @return groups Number of complete groups in the run.
156
- /// @return end Absolute position immediately after the run.
157
- function scope(
158
- uint abs,
159
- uint limit,
160
- bytes4 key,
161
- uint stride
162
- ) internal pure returns (uint groups, uint end) {
163
- if (stride == 0) revert ZeroStride();
164
- uint count;
165
- (count, end) = run(abs, limit, key);
166
- if (count == 0) revert EmptyRun();
167
- if (count % stride != 0) revert BadRatio();
168
- groups = count / stride;
150
+ /// @param key Required block key for the complete region.
151
+ /// @return total Number of matching blocks.
152
+ /// @return end Absolute position equal to `limit`.
153
+ function runExact(uint abs, uint limit, bytes4 key) internal pure returns (uint total, uint end) {
154
+ (total, end) = run(abs, limit, key);
155
+ if (end != limit) revert InvalidBlock();
169
156
  }
170
157
 
171
158
  // Generic block writes
@@ -460,6 +447,33 @@ library Blocks {
460
447
 
461
448
  // Four-word payloads
462
449
 
450
+ /// @notice Write a POSITION block at `i`.
451
+ /// @dev DANGER: Unchecked memory write. Reserve `Sizes.B128` bytes first.
452
+ /// @param dst Destination buffer.
453
+ /// @param i Relative write position.
454
+ /// @param asset Identifier for the asset side.
455
+ /// @param amount Quantity on the asset side.
456
+ /// @param liability Identifier for the liability side.
457
+ /// @param debt Quantity owed on the liability side.
458
+ function writePosition(
459
+ bytes memory dst,
460
+ uint i,
461
+ bytes32 asset,
462
+ uint amount,
463
+ bytes32 liability,
464
+ uint debt
465
+ ) internal pure {
466
+ uint spec = Specs.Position;
467
+ assembly ("memory-safe") {
468
+ let p := add(add(dst, 0x20), i)
469
+ mstore(p, spec)
470
+ mstore(add(p, 0x08), asset)
471
+ mstore(add(p, 0x28), amount)
472
+ mstore(add(p, 0x48), liability)
473
+ mstore(add(p, 0x68), debt)
474
+ }
475
+ }
476
+
463
477
  /// @notice Write a TRANSACTION block at `i`.
464
478
  /// @dev DANGER: Unchecked memory write. Reserve `Sizes.B128` bytes first.
465
479
  /// @param dst Destination buffer.
@@ -754,20 +768,18 @@ library Blocks {
754
768
  /// block size and ensure the encoded payload length fits in uint32.
755
769
  /// @param dst Destination buffer.
756
770
  /// @param i Relative write position.
757
- /// @param id Node identifier.
758
771
  /// @param namespace Label namespace.
759
772
  /// @param name Label text.
760
- function writeLabel(bytes memory dst, uint i, uint id, bytes32 namespace, string memory name) internal pure {
761
- uint len = 64 + Sizes.Header + bytes(name).length;
773
+ function writeLabel(bytes memory dst, uint i, bytes32 namespace, string memory name) internal pure {
774
+ uint len = 32 + Sizes.Header + bytes(name).length;
762
775
  uint key = uint32(Keys.Label);
763
776
  uint stringkey = uint32(Keys.String);
764
777
  assembly ("memory-safe") {
765
778
  let p := add(add(dst, 0x20), i)
766
779
  mstore(p, or(shl(224, key), shl(192, len)))
767
- mstore(add(p, 0x08), id)
768
- mstore(add(p, 0x28), namespace)
780
+ mstore(add(p, 0x08), namespace)
769
781
 
770
- let q := add(p, 0x48)
782
+ let q := add(p, 0x28)
771
783
  let namelen := mload(name)
772
784
  mstore(q, or(shl(224, stringkey), shl(192, namelen)))
773
785
  mcopy(add(q, 0x08), add(name, 0x20), namelen)
@@ -1251,6 +1263,28 @@ library Blocks {
1251
1263
 
1252
1264
  // Four-word payloads
1253
1265
 
1266
+ /// @notice Decode a low-level fixed-width POSITION block at `abs`.
1267
+ /// @param abs Absolute block position.
1268
+ /// @return asset Decoded asset-side identifier.
1269
+ /// @return amount Decoded asset-side quantity.
1270
+ /// @return liability Decoded liability-side identifier.
1271
+ /// @return debt Decoded liability-side debt.
1272
+ function unpackPosition(
1273
+ uint abs
1274
+ ) internal pure returns (bytes32 asset, uint amount, bytes32 liability, uint debt) {
1275
+ uint head;
1276
+ assembly ("memory-safe") {
1277
+ head := calldataload(abs)
1278
+ }
1279
+ if (head >> 192 != Specs.Position >> 192) revert InvalidBlock();
1280
+ assembly ("memory-safe") {
1281
+ asset := calldataload(add(abs, 0x08))
1282
+ amount := calldataload(add(abs, 0x28))
1283
+ liability := calldataload(add(abs, 0x48))
1284
+ debt := calldataload(add(abs, 0x68))
1285
+ }
1286
+ }
1287
+
1254
1288
  /// @notice Decode a low-level fixed-width TRANSACTION block at `abs`.
1255
1289
  /// @param abs Absolute block position.
1256
1290
  /// @return from Decoded debit account.
@@ -1345,6 +1379,23 @@ library Blocks {
1345
1379
 
1346
1380
  // One fixed word
1347
1381
 
1382
+ /// @notice Decode one ANNOTATION block and its nested block stream.
1383
+ /// @param abs Absolute block position.
1384
+ /// @return entity Decoded entity identifier.
1385
+ /// @return stream Decoded annotation block stream.
1386
+ /// @return end Absolute position after the block.
1387
+ function unpackAnnotation(
1388
+ uint abs
1389
+ ) internal pure returns (uint entity, bytes calldata stream, uint end) {
1390
+ uint limit;
1391
+ (abs, limit) = expect(abs, Specs.Annotation);
1392
+ assembly ("memory-safe") {
1393
+ entity := calldataload(abs)
1394
+ }
1395
+ (stream, end) = unpackBytes(abs + 32);
1396
+ if (end != limit) revert InvalidBlock();
1397
+ }
1398
+
1348
1399
  /// @notice Decode one CONTEXT block and all nested byte blocks.
1349
1400
  /// @param abs Absolute block position.
1350
1401
  /// @return account Decoded account identifier.
@@ -1444,19 +1495,17 @@ library Blocks {
1444
1495
 
1445
1496
  /// @notice Decode one LABEL block and its nested name.
1446
1497
  /// @param abs Absolute block position.
1447
- /// @return id Decoded node identifier.
1448
1498
  /// @return namespace Decoded label namespace.
1449
1499
  /// @return name Decoded label text.
1450
1500
  /// @return end Absolute position after the block.
1451
- function unpackLabel(uint abs) internal pure returns (uint id, bytes32 namespace, string memory name, uint end) {
1501
+ function unpackLabel(uint abs) internal pure returns (bytes32 namespace, string memory name, uint end) {
1452
1502
  uint limit;
1453
1503
  (abs, limit) = expect(abs, Specs.Label);
1454
1504
  assembly ("memory-safe") {
1455
- id := calldataload(abs)
1456
- namespace := calldataload(add(abs, 0x20))
1505
+ namespace := calldataload(abs)
1457
1506
  }
1458
1507
  bytes calldata value;
1459
- (value, end) = unpackString(abs + 64);
1508
+ (value, end) = unpackString(abs + 32);
1460
1509
  if (end != limit) revert InvalidBlock();
1461
1510
  name = string(value);
1462
1511
  }
@@ -1532,6 +1581,30 @@ library Blocks {
1532
1581
  return create(Keys.String, bytes(value));
1533
1582
  }
1534
1583
 
1584
+ /// @notice Encode a LABEL block.
1585
+ /// @param namespace Label namespace.
1586
+ /// @param name Label text.
1587
+ /// @return Encoded LABEL block bytes.
1588
+ function label(bytes32 namespace, string memory name) internal pure returns (bytes memory) {
1589
+ return create(Keys.Label, bytes.concat(namespace, text(name)));
1590
+ }
1591
+
1592
+ /// @notice Encode an ACTION annotation block.
1593
+ /// @param value Canonical semantic action identifier.
1594
+ /// @return Encoded ACTION block bytes.
1595
+ function action(uint value) internal pure returns (bytes memory) {
1596
+ return create(Keys.Action, bytes.concat(bytes32(value)));
1597
+ }
1598
+
1599
+ /// @notice Encode a SCHEMA block.
1600
+ /// @param spec Block specification.
1601
+ /// @param body Schema body.
1602
+ /// @param name Schema name.
1603
+ /// @return Encoded SCHEMA block bytes.
1604
+ function schema(uint spec, string memory body, bytes32 name) internal pure returns (bytes memory) {
1605
+ return create(Keys.Schema, bytes.concat(bytes32(spec), text(body), name));
1606
+ }
1607
+
1535
1608
  /// @notice Encode a BALANCE block.
1536
1609
  /// @param asset Asset identifier.
1537
1610
  /// @param amount Token amount.
@@ -1540,6 +1613,28 @@ library Blocks {
1540
1613
  return bytes.concat(Keys.Balance, bytes4(uint32(64)), asset, bytes32(amount));
1541
1614
  }
1542
1615
 
1616
+ /// @notice Encode a POSITION block.
1617
+ /// @param asset Identifier for the asset side.
1618
+ /// @param amount Quantity on the asset side.
1619
+ /// @param liability Identifier for the liability side.
1620
+ /// @param debt Quantity owed on the liability side.
1621
+ /// @return Encoded POSITION block bytes.
1622
+ function position(
1623
+ bytes32 asset,
1624
+ uint amount,
1625
+ bytes32 liability,
1626
+ uint debt
1627
+ ) internal pure returns (bytes memory) {
1628
+ return bytes.concat(
1629
+ Keys.Position,
1630
+ bytes4(uint32(128)),
1631
+ asset,
1632
+ bytes32(amount),
1633
+ liability,
1634
+ bytes32(debt)
1635
+ );
1636
+ }
1637
+
1543
1638
  /// @notice Encode a CUSTODY block.
1544
1639
  /// @param host Host node ID holding the custody.
1545
1640
  /// @param asset Asset identifier.
package/codec/Buffers.sol CHANGED
@@ -18,12 +18,12 @@ library Buffers {
18
18
 
19
19
  /// @notice Create a packed buffer cursor at write position zero.
20
20
  /// @param len Initial logical byte capacity.
21
- /// @param groups Number of logical groups represented by the buffer.
21
+ /// @param count Number of logical items represented by the buffer.
22
22
  /// @param growable Whether writes may expand the logical capacity.
23
23
  /// @param tag Cursor identity tag.
24
24
  /// @return cur Packed buffer cursor.
25
- function cursor(uint len, uint groups, bool growable, uint8 tag) internal pure returns (uint cur) {
26
- cur = Cursors.create(0, len, groups, growable ? Growable : 0, tag);
25
+ function cursor(uint len, uint count, bool growable, uint8 tag) internal pure returns (uint cur) {
26
+ cur = Cursors.create(0, len, count, growable ? Growable : 0, tag);
27
27
  }
28
28
 
29
29
  /// @notice Reserve relative write space and return the updated packed buffer cursor.
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import {AssetAmount, AccountAsset, AccountAmount, HostAmount, HostAccountAsset, Tx} from "../core/Types.sol";
4
+ import {AssetAmount, AccountAsset, AccountAmount, HostAmount, HostAccountAsset, Position, Tx} from "../core/Types.sol";
5
5
  import {Blocks} from "./Blocks.sol";
6
6
  import {Sizes, Specs} from "./Specs.sol";
7
7
  import {Cursors, Cur} from "../utils/Cursors.sol";
@@ -32,20 +32,17 @@ library Decoders {
32
32
  cur.state = Cursors.wrap(source[i:], 0, 0);
33
33
  }
34
34
 
35
- /// @notice Open the first homogeneous run in `source` using `stride`.
35
+ /// @notice Open the first homogeneous run in `source`.
36
36
  /// @param source Calldata block stream to open.
37
- /// @param stride Number of blocks per group, or zero for an ungrouped stream.
38
37
  /// @return cur Cursor spanning the first homogeneous run.
39
- function open(
40
- bytes calldata source,
41
- uint stride
42
- ) internal pure returns (Cur memory cur) {
38
+ function open(bytes calldata source) internal pure returns (Cur memory cur) {
43
39
  (uint abs, uint limit) = Cursors.bounds(source);
44
- if (stride == 0 && abs == limit) return cur;
40
+ if (abs == limit) revert Blocks.EmptyRun();
45
41
 
46
42
  bytes4 key = bytes4(source);
47
- (uint groups, uint end) = Blocks.scope(abs, limit, key, stride);
48
- cur.state = Cursors.create(abs, end - abs, groups, 0, 0);
43
+ (uint count, uint end) = Blocks.run(abs, limit, key);
44
+ if (count == 0) revert Blocks.EmptyRun();
45
+ cur.state = Cursors.create(abs, end - abs, count, 0, 0);
49
46
  }
50
47
 
51
48
  /// @notice Return whether `cur` has unread bytes.
@@ -163,6 +160,16 @@ library Decoders {
163
160
  return Blocks.find(offset + i, offset + end, key) - offset;
164
161
  }
165
162
 
163
+ /// @notice Count consecutive blocks with `key` from the current cursor position.
164
+ /// @dev Does not advance the cursor.
165
+ /// @param cur Cursor to inspect.
166
+ /// @param key Block key forming the run.
167
+ /// @return count Number of consecutive matching blocks.
168
+ function run(Cur memory cur, bytes4 key) internal pure returns (uint count) {
169
+ (uint i, uint offset, uint len) = cur.state.decode();
170
+ (count, ) = Blocks.run(offset + i, offset + len, key);
171
+ }
172
+
166
173
  /// @notice Consume one LIST block and return a cursor over its items.
167
174
  /// @param cur Cursor advanced past the list.
168
175
  /// @return items Cursor spanning the list payload.
@@ -365,6 +372,19 @@ library Decoders {
365
372
  cur.state = cur.state.seekAbs(end);
366
373
  }
367
374
 
375
+ /// @notice Decode and consume one ANNOTATION block.
376
+ /// @param cur Cursor advanced past the block.
377
+ /// @return entity Decoded entity identifier.
378
+ /// @return data Decoded annotation block stream.
379
+ function unpackAnnotation(
380
+ Cur memory cur
381
+ ) internal pure returns (uint entity, bytes calldata data) {
382
+ uint abs = cur.state.absolute();
383
+ uint end;
384
+ (entity, data, end) = Blocks.unpackAnnotation(abs);
385
+ cur.state = cur.state.seekAbs(end);
386
+ }
387
+
368
388
  /// @notice Decode and consume one CONTEXT block.
369
389
  /// @param cur Cursor advanced past the block.
370
390
  /// @return account Decoded account identifier.
@@ -395,13 +415,12 @@ library Decoders {
395
415
 
396
416
  /// @notice Decode and consume one LABEL block.
397
417
  /// @param cur Cursor advanced past the block.
398
- /// @return id Decoded node identifier.
399
418
  /// @return namespace Decoded label namespace.
400
419
  /// @return name Decoded label text.
401
- function unpackLabel(Cur memory cur) internal pure returns (uint id, bytes32 namespace, string memory name) {
420
+ function unpackLabel(Cur memory cur) internal pure returns (bytes32 namespace, string memory name) {
402
421
  uint abs = cur.state.absolute();
403
422
  uint end;
404
- (id, namespace, name, end) = Blocks.unpackLabel(abs);
423
+ (namespace, name, end) = Blocks.unpackLabel(abs);
405
424
  cur.state = cur.state.seekAbs(end);
406
425
  }
407
426
 
@@ -520,6 +539,20 @@ library Decoders {
520
539
  (value.host, value.asset, value.amount) = unpackCustody(cur);
521
540
  }
522
541
 
542
+ /// @notice Decode and consume one POSITION block.
543
+ function unpackPosition(
544
+ Cur memory cur
545
+ ) internal pure returns (bytes32 asset, uint amount, bytes32 liability, uint debt) {
546
+ uint abs;
547
+ (cur.state, abs) = cur.state.consume(Sizes.Position);
548
+ (asset, amount, liability, debt) = Blocks.unpackPosition(abs);
549
+ }
550
+
551
+ /// @notice Decode one POSITION block into its structured value.
552
+ function unpackPositionValue(Cur memory cur) internal pure returns (Position memory value) {
553
+ (value.asset, value.amount, value.liability, value.debt) = unpackPosition(cur);
554
+ }
555
+
523
556
  /// @notice Decode one TRANSACTION block into its structured value.
524
557
  /// @param cur Cursor advanced past the block.
525
558
  /// @return value Structured transaction.
@@ -37,7 +37,6 @@ library Descriptors {
37
37
  state = Specs.normalize(state, true);
38
38
  input = Specs.normalize(input, false);
39
39
  output = Specs.normalize(output, true);
40
-
41
40
  descriptor = pack(state, input, output, transactions, flags);
42
41
  }
43
42
 
package/codec/Keys.sol CHANGED
@@ -5,12 +5,10 @@ pragma solidity ^0.8.33;
5
5
  /// @notice Standard block type selectors for the rootzero block stream protocol.
6
6
  /// Standard keys use the first 4 bytes of `keccak256("#name")` by convention.
7
7
  /// Custom block keys only need to be unique in the context where they are used;
8
- /// hosts may publish custom key meanings with the `Schema` event.
8
+ /// hosts may publish custom key meanings with `#schema` annotations.
9
9
  library Keys {
10
10
  /// @dev Empty / unset key.
11
11
  bytes4 constant Empty = bytes4(0);
12
- /// @dev Wildcard key used in discovery when any block stream is accepted.
13
- bytes4 constant Any = 0xffffffff;
14
12
  /// @dev Input amount - (bytes32 asset, uint amount)
15
13
  bytes4 constant Amount = bytes4(keccak256("#amount"));
16
14
  /// @dev Ledger balance - (bytes32 asset, uint amount)
@@ -21,6 +19,8 @@ library Keys {
21
19
  bytes4 constant Allowance = bytes4(keccak256("#allowance"));
22
20
  /// @dev Cross-host custody state - (uint host, bytes32 asset, uint amount)
23
21
  bytes4 constant Custody = bytes4(keccak256("#custody"));
22
+ /// @dev Asset-liability position state - (bytes32 asset, uint amount, bytes32 liability, uint debt)
23
+ bytes4 constant Position = bytes4(keccak256("#position"));
24
24
  /// @dev List wrapper; payload is an embedded repeated block stream
25
25
  bytes4 constant List = bytes4(keccak256("#list"));
26
26
  /// @dev EVM-encoded payload field; layout follows standard ABI tuple encoding
@@ -49,8 +49,12 @@ library Keys {
49
49
  bytes4 constant Asset = bytes4(keccak256("#asset"));
50
50
  /// @dev Node identifier - (uint id)
51
51
  bytes4 constant Node = bytes4(keccak256("#node"));
52
- /// @dev Mutable node label - (uint id, bytes32 namespace, #string as name)
52
+ /// @dev Entity label annotation - (bytes32 namespace, #string as name)
53
53
  bytes4 constant Label = bytes4(keccak256("#label"));
54
+ /// @dev Entity annotations - (uint entity, #bytes as data)
55
+ bytes4 constant Annotation = bytes4(keccak256("#annotation"));
56
+ /// @dev Primary semantic action annotation - (uint action)
57
+ bytes4 constant Action = bytes4(keccak256("#action"));
54
58
  /// @dev Block schema publication - (bytes4 key, #string as body, bytes32 name)
55
59
  bytes4 constant Schema = bytes4(keccak256("#schema"));
56
60
 
package/codec/Readers.sol CHANGED
@@ -94,6 +94,19 @@ library Readers {
94
94
  }
95
95
  }
96
96
 
97
+ /// @notice Consume a POSITION block and return its fields.
98
+ function unpackPosition(
99
+ Reader memory cur
100
+ ) internal pure returns (bytes32 asset, uint amount, bytes32 liability, uint debt) {
101
+ uint abs = consume(cur, Keys.Position, 128, 128);
102
+ assembly ("memory-safe") {
103
+ asset := mload(abs)
104
+ amount := mload(add(abs, 0x20))
105
+ liability := mload(add(abs, 0x40))
106
+ debt := mload(add(abs, 0x60))
107
+ }
108
+ }
109
+
97
110
  /// @notice Consume a TRANSACTION block and return its fields.
98
111
  /// @param cur Reader; advanced past the block.
99
112
  /// @return from Source account identifier.
package/codec/Schema.sol CHANGED
@@ -36,17 +36,20 @@ pragma solidity ^0.8.33;
36
36
  // - generic lists use the stable key derived from `#list`
37
37
  // - standard keys are derived from block aliases, e.g. bytes4(keccak256("#amount"))
38
38
  // - custom keys are opaque bytes4 tags and only need to be unique in their
39
- // active context; use `Schema(host, spec, schema, name)` to publish their meaning
39
+ // active context; use a `#schema` annotation to publish their meaning
40
40
  // - see `docs/Schema.md` for the full working spec
41
41
  //
42
42
  // Pipeline state:
43
43
  // - command input and state streams are each a single run of blocks under the
44
44
  // current protocol convention; the block format may support other shapes in
45
45
  // future protocol surfaces
46
- // - `balance(...)` and `custody(...)` are live, linear state in the active command pipeline
46
+ // - `balance(...)`, `custody(...)`, and `position(...)` are live, linear state in the active command pipeline
47
47
  // - pipeline state belongs to the active account while the pipeline is executing
48
48
  // - while a balance or custody is in-flight as pipeline state, it is not simultaneously persisted
49
49
  // in another ledger/store by this protocol
50
+ // - a position pairs live asset and liability sides; commands may transform either side
51
+ // - position state is transient and does not itself create or erase an externally persisted obligation
52
+ // - positions support backward composition, but pipeline steps always execute in encoded order
50
53
  // - commands must preserve, transform, settle, or intentionally consume pipeline state
51
54
  // - input blocks such as `amount(...)`, `allocation(...)`, and `allowance(...)`
52
55
  // express intent, constraints, or references
@@ -90,6 +93,7 @@ library Schemas {
90
93
 
91
94
  // Four-word payloads
92
95
 
96
+ string constant Position = "{ bytes32 asset, uint amount, bytes32 liability, uint debt }";
93
97
  string constant Transaction = "{ bytes32 from, bytes32 to, bytes32 asset, uint amount }";
94
98
  string constant HostAccountAmount = "{ uint host, bytes32 account, bytes32 asset, uint amount }";
95
99
 
@@ -101,7 +105,12 @@ library Schemas {
101
105
  string constant Dispatch = "{ uint portal, uint resources, #bytes as payload }";
102
106
  string constant Context = "{ bytes32 account, #bytes as state, #bytes as input }";
103
107
  string constant Recover = "{ uint handler, uint resources, bytes32 key, #bytes as witness }";
104
- string constant Label = "{ uint id, bytes32 namespace, #string as name }";
108
+ string constant Annotation = "{ uint entity, #bytes as data }";
109
+
110
+ // Annotation payloads
111
+
112
+ string constant Action = "{ uint action }";
113
+ string constant Label = "{ bytes32 namespace, #string as name }";
105
114
  string constant Schema = "{ uint spec, #string as body, bytes32 name }";
106
115
  }
107
116
 
package/codec/Specs.sol CHANGED
@@ -31,6 +31,8 @@ library Sizes {
31
31
  uint constant HostAmount = B96;
32
32
  /// @dev TRANSACTION block: 8 header + 32 from + 32 to + 32 asset + 32 amount = 136 bytes
33
33
  uint constant Transaction = B128;
34
+ /// @dev POSITION block: 8 header + four-word asset-liability pair = 136 bytes
35
+ uint constant Position = B128;
34
36
  }
35
37
 
36
38
  /// @title Specs
@@ -44,7 +46,6 @@ library Specs {
44
46
  error InvalidSpec();
45
47
  /// @dev A direct specification cannot contain a wrapper block.
46
48
  error InvalidContainer();
47
-
48
49
  uint private constant SizeFields = (uint(1) << 192) | (uint(1) << 160) | (uint(1) << 136);
49
50
 
50
51
  // Reusable field shapes keep public specs readable while remaining valid
@@ -54,17 +55,18 @@ library Specs {
54
55
  uint private constant Exact96 = 96 * SizeFields;
55
56
  uint private constant Exact128 = 128 * SizeFields;
56
57
  uint private constant UnboundedHint128 = uint(128) << 136;
58
+ uint private constant UnboundedMin40Hint256 = (uint(40) << 192) | (uint(256) << 136);
57
59
  uint private constant UnboundedMin72Hint256 = (uint(72) << 192) | (uint(256) << 136);
58
60
  uint private constant UnboundedMin48Hint512 = (uint(48) << 192) | (uint(512) << 136);
59
61
  uint private constant UnboundedMin104Hint256 = (uint(104) << 192) | (uint(256) << 136);
60
62
 
61
63
  uint constant Empty = uint(bytes32(Keys.Empty));
62
- uint constant Any = uint(bytes32(Keys.Any)) | UnboundedHint128;
63
64
  uint constant Amount = uint(bytes32(Keys.Amount)) | Exact64;
64
65
  uint constant Balance = uint(bytes32(Keys.Balance)) | Exact64;
65
66
  uint constant Allocation = uint(bytes32(Keys.Allocation)) | Exact96;
66
67
  uint constant Allowance = uint(bytes32(Keys.Allowance)) | Exact96;
67
68
  uint constant Custody = uint(bytes32(Keys.Custody)) | Exact96;
69
+ uint constant Position = uint(bytes32(Keys.Position)) | Exact128;
68
70
  uint constant List = uint(bytes32(Keys.List)) | UnboundedHint128;
69
71
  uint constant Evm = uint(bytes32(Keys.Evm)) | UnboundedHint128;
70
72
  uint constant Bytes = uint(bytes32(Keys.Bytes)) | UnboundedHint128;
@@ -79,7 +81,9 @@ library Specs {
79
81
  uint constant Call = uint(bytes32(Keys.Call)) | UnboundedMin72Hint256;
80
82
  uint constant Asset = uint(bytes32(Keys.Asset)) | Exact32;
81
83
  uint constant Node = uint(bytes32(Keys.Node)) | Exact32;
82
- uint constant Label = uint(bytes32(Keys.Label)) | UnboundedMin72Hint256;
84
+ uint constant Label = uint(bytes32(Keys.Label)) | UnboundedMin40Hint256;
85
+ uint constant Annotation = uint(bytes32(Keys.Annotation)) | UnboundedMin40Hint256;
86
+ uint constant Action = uint(bytes32(Keys.Action)) | Exact32;
83
87
  uint constant Schema = uint(bytes32(Keys.Schema)) | UnboundedMin72Hint256;
84
88
 
85
89
  uint constant Status = uint(bytes32(Keys.Status)) | Exact32;
@@ -96,12 +100,31 @@ library Specs {
96
100
  /// @param hint Initial per-block payload capacity.
97
101
  /// @return spec Packed block specification.
98
102
  function create(bytes4 blockkey, uint32 min, uint32 max, uint32 hint) internal pure returns (uint spec) {
99
- spec |= uint(uint32(blockkey)) << 224;
103
+ return create(uint32(blockkey), min, max, hint);
104
+ }
105
+
106
+ /// @notice Construct a block specification from its numeric key and encoded fields.
107
+ /// @param blockkey Numeric block key.
108
+ /// @param min Minimum accepted payload length.
109
+ /// @param max Maximum accepted payload length; zero means unbounded.
110
+ /// @param hint Initial per-block payload capacity.
111
+ /// @return spec Packed block specification.
112
+ function create(uint32 blockkey, uint32 min, uint32 max, uint32 hint) internal pure returns (uint spec) {
113
+ spec |= uint(blockkey) << 224;
100
114
  spec |= uint(min) << 192;
101
115
  spec |= uint(max) << 160;
102
116
  spec |= uint(max24(hint)) << 136;
103
117
  }
104
118
 
119
+ /// @notice Construct an exact-size block specification from a numeric key.
120
+ /// @dev Sets the minimum, maximum, and allocation hint to `size`.
121
+ /// @param blockkey Numeric block key.
122
+ /// @param size Exact payload length and initial per-block payload capacity.
123
+ /// @return spec Packed exact-size block specification.
124
+ function create(uint32 blockkey, uint32 size) internal pure returns (uint spec) {
125
+ return create(blockkey, size, size, size);
126
+ }
127
+
105
128
  /// @notice Decode the block key and accepted payload range from `spec`.
106
129
  /// @param spec Packed block specification.
107
130
  /// @return blockkey Encoded block key.
@@ -215,4 +238,5 @@ library Specs {
215
238
  function group(uint spec, uint8 n) internal pure returns (uint) {
216
239
  return replace8(spec, 128, n);
217
240
  }
241
+
218
242
  }
package/codec/Writers.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 {AssetAmount, AccountAmount, HostAmount, Tx} from "../core/Types.sol";
4
+ import {AssetAmount, AccountAmount, HostAmount, Position, Tx} from "../core/Types.sol";
5
5
  import {Blocks} from "./Blocks.sol";
6
6
  import {Buffers} from "./Buffers.sol";
7
7
  import {Sizes, Specs} from "./Specs.sol";
@@ -44,7 +44,7 @@ library Writers {
44
44
  (uint capacity, bool growable) = Specs.allocation(spec, groups);
45
45
  if (capacity == 0) return writer;
46
46
 
47
- writer.cur = Buffers.cursor(capacity, groups, growable, 0);
47
+ writer.cur = Buffers.cursor(capacity, Specs.count(spec, groups), growable, 0);
48
48
  }
49
49
 
50
50
  // -------------------------------------------------------------------------
@@ -291,6 +291,23 @@ library Writers {
291
291
  Blocks.writeHostAccountAsset(writer.dst, i, host, account, asset);
292
292
  }
293
293
 
294
+ /// @notice Append a POSITION block.
295
+ function appendPosition(
296
+ Writer memory writer,
297
+ bytes32 asset,
298
+ uint amount,
299
+ bytes32 liability,
300
+ uint debt
301
+ ) internal pure {
302
+ uint i = reserve(writer, Sizes.Position);
303
+ Blocks.writePosition(writer.dst, i, asset, amount, liability, debt);
304
+ }
305
+
306
+ /// @notice Append a structured POSITION value.
307
+ function appendPosition(Writer memory writer, Position memory value) internal pure {
308
+ appendPosition(writer, value.asset, value.amount, value.liability, value.debt);
309
+ }
310
+
294
311
  /// @notice Append a TRANSACTION block.
295
312
  /// @param writer Destination writer.
296
313
  /// @param from Debit account identifier.
@@ -448,18 +465,16 @@ library Writers {
448
465
 
449
466
  /// @notice Append a LABEL block.
450
467
  /// @param writer Destination writer.
451
- /// @param id Node identifier to encode.
452
468
  /// @param namespace Label namespace to encode.
453
469
  /// @param name Label text to encode.
454
470
  function appendLabel(
455
471
  Writer memory writer,
456
- uint id,
457
472
  bytes32 namespace,
458
473
  string memory name
459
474
  ) internal pure {
460
- uint size = Sizes.B64 + Sizes.Header + bytes(name).length;
475
+ uint size = Sizes.B32 + Sizes.Header + bytes(name).length;
461
476
  uint i = reserve(writer, size);
462
- Blocks.writeLabel(writer.dst, i, id, namespace, name);
477
+ Blocks.writeLabel(writer.dst, i, namespace, name);
463
478
  }
464
479
 
465
480
  /// @notice Append a SCHEMA block.