@rootzero/contracts 0.4.0 → 0.5.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.
package/Cursors.sol CHANGED
@@ -1,14 +1,13 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- // Aggregator: re-exports all block stream primitives (Cursors, Writers, Mem, Schema, Keys).
4
+ // Aggregator: re-exports all block stream primitives (Cursors, Writers, Schema, Keys).
5
5
  // Import this file to get access to the full block encoding/decoding surface in one import.
6
6
 
7
7
  import { HostAmount, UserAmount, HostAsset, Tx, AssetAmount } from "./blocks/Schema.sol";
8
8
  import { Keys } from "./blocks/Keys.sol";
9
9
  import { Schemas } from "./blocks/Schema.sol";
10
10
  import { Cursors, Cur } from "./blocks/Cursors.sol";
11
- import { Mem, MemRef } from "./blocks/Mem.sol";
12
11
  import { Writer, Writers } from "./blocks/Writers.sol";
13
12
 
14
13
 
@@ -778,6 +778,15 @@ library Cursors {
778
778
  cur.i += 104;
779
779
  }
780
780
 
781
+ /// @notice Consume a MINIMUM block, assert it matches the expected asset and meta, and require `amount` to satisfy it.
782
+ /// @param cur Cursor; advanced past the block.
783
+ /// @param asset Expected asset identifier.
784
+ /// @param meta Expected metadata slot.
785
+ /// @param amount Actual amount that must be at least the minimum from the block.
786
+ function requireMinimum(Cur memory cur, bytes32 asset, bytes32 meta, uint amount) internal pure {
787
+ if (requireMinimum(cur, asset, meta) > amount) revert UnexpectedValue();
788
+ }
789
+
781
790
  /// @notice Consume a MAXIMUM block and assert it matches the expected asset and meta.
782
791
  /// @param cur Cursor; advanced past the block.
783
792
  /// @param asset Expected asset identifier.
@@ -788,6 +797,15 @@ library Cursors {
788
797
  cur.i += 104;
789
798
  }
790
799
 
800
+ /// @notice Consume a MAXIMUM block, assert it matches the expected asset and meta, and require `amount` to satisfy it.
801
+ /// @param cur Cursor; advanced past the block.
802
+ /// @param asset Expected asset identifier.
803
+ /// @param meta Expected metadata slot.
804
+ /// @param amount Actual amount that must be at most the maximum from the block.
805
+ function requireMaximum(Cur memory cur, bytes32 asset, bytes32 meta, uint amount) internal pure {
806
+ if (requireMaximum(cur, asset, meta) < amount) revert UnexpectedValue();
807
+ }
808
+
791
809
  /// @notice Consume a CUSTODY block and assert it belongs to the expected host.
792
810
  /// @param cur Cursor; advanced past the block.
793
811
  /// @param host Expected host node ID.
package/blocks/Keys.sol CHANGED
@@ -30,7 +30,7 @@ library Keys {
30
30
  bytes4 constant Party = bytes4(keccak256("party(bytes32 account)"));
31
31
  /// @dev Destination account — (bytes32 account)
32
32
  bytes4 constant Recipient = bytes4(keccak256("recipient(bytes32 account)"));
33
- /// @dev Settled transfer record — (bytes32 from, bytes32 to, bytes32 asset, bytes32 meta, uint amount)
33
+ /// @dev Transfer record passed through the pipeline — (bytes32 from, bytes32 to, bytes32 asset, bytes32 meta, uint amount)
34
34
  bytes4 constant Transaction = bytes4(keccak256("tx(bytes32 from, bytes32 to, bytes32 asset, bytes32 meta, uint amount)"));
35
35
  /// @dev Sub-command invocation — (uint target, uint value, bytes request)
36
36
  bytes4 constant Step = bytes4(keccak256("step(uint target, uint value, bytes request)"));
package/blocks/Schema.sol CHANGED
@@ -143,7 +143,7 @@ struct HostAsset {
143
143
  bytes32 meta;
144
144
  }
145
145
 
146
- /// @notice Settled transfer record written to transaction state.
146
+ /// @notice Transfer payload used across the pipeline and later consumed by settlement.
147
147
  struct Tx {
148
148
  /// @dev Sender account identifier.
149
149
  bytes32 from;
@@ -259,7 +259,7 @@ library Writers {
259
259
  /// @notice Write a TRANSACTION block directly into `dst` at byte offset `i`.
260
260
  /// @param dst Destination buffer; must have at least `i + Sizes.Transaction` bytes.
261
261
  /// @param i Write offset within `dst`.
262
- /// @param value Transaction fields to encode.
262
+ /// @param value Transfer record fields to encode.
263
263
  /// @return next Byte offset immediately after the written block.
264
264
  function writeTxBlock(bytes memory dst, uint i, Tx memory value) internal pure returns (uint next) {
265
265
  next = i + Sizes.Transaction;
@@ -279,7 +279,7 @@ library Writers {
279
279
 
280
280
  /// @notice Append a TRANSACTION block from a struct.
281
281
  /// @param writer Destination writer; `i` is advanced by `Sizes.Transaction`.
282
- /// @param value Transaction fields to encode.
282
+ /// @param value Transfer record fields to encode.
283
283
  function appendTx(Writer memory writer, Tx memory value) internal pure {
284
284
  writer.i = writeTxBlock(writer.dst, writer.i, value);
285
285
  }
@@ -2,7 +2,7 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import {CommandContext, CommandBase, State} from "./Base.sol";
5
- import {Cursors, Cur, Keys, Schemas, Writer, Writers} from "../Cursors.sol";
5
+ import {Cursors, Cur, Schemas, Writer, Writers} from "../Cursors.sol";
6
6
  using Cursors for Cur;
7
7
  using Writers for Writer;
8
8
 
@@ -38,8 +38,6 @@ abstract contract Provision is CommandBase, ProvisionHook {
38
38
  CommandContext calldata c
39
39
  ) external payable onlyCommand(provisionId, c.target) returns (bytes memory) {
40
40
  (Cur memory request, uint count, ) = cursor(c.request, 1);
41
- (bytes4 key, ) = request.peek(0);
42
- if (key != Keys.Bundle) revert Writers.EmptyRequest();
43
41
  Writer memory writer = Writers.allocCustodies(count);
44
42
 
45
43
  while (request.i < request.bound) {
@@ -3,31 +3,27 @@ pragma solidity ^0.8.33;
3
3
 
4
4
  import { CommandContext, CommandBase, State } from "./Base.sol";
5
5
  import { Cursors, Cur, Tx } from "../Cursors.sol";
6
+ import { TransferHook } from "./Transfer.sol";
6
7
  using Cursors for Cur;
7
8
 
8
9
  string constant NAME = "settle";
9
10
 
10
11
  /// @title Settle
11
- /// @notice Command that settles each TRANSACTION state block via a virtual hook.
12
+ /// @notice Command that consumes each TRANSACTION state block and settles it through the shared transfer hook.
12
13
  /// Produces no output state.
13
- abstract contract Settle is CommandBase {
14
+ abstract contract Settle is CommandBase, TransferHook {
14
15
  uint internal immutable settleId = commandId(NAME);
15
16
 
16
17
  constructor() {
17
18
  emit Command(host, NAME, "", settleId, State.Transactions, State.Empty);
18
19
  }
19
20
 
20
- /// @notice Override to settle a single transaction block.
21
- /// Called once per TRANSACTION block in state.
22
- /// @param value Decoded transaction (from, to, asset, meta, amount).
23
- function settle(Tx memory value) internal virtual;
24
-
25
21
  function settle(CommandContext calldata c) external payable onlyCommand(settleId, c.target) returns (bytes memory) {
26
22
  (Cur memory state, , ) = cursor(c.state, 1);
27
23
 
28
24
  while (state.i < state.bound) {
29
25
  Tx memory value = state.unpackTxValue();
30
- settle(value);
26
+ transfer(value);
31
27
  }
32
28
 
33
29
  state.complete();
@@ -2,39 +2,46 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import { CommandContext, CommandBase, State } from "./Base.sol";
5
- import { Cursors, Cur, Schemas } from "../Cursors.sol";
5
+ import { Cursors, Cur, Schemas, Tx } from "../Cursors.sol";
6
+ import { Accounts } from "../utils/Accounts.sol";
6
7
  using Cursors for Cur;
7
8
 
8
9
  string constant NAME = "transfer";
9
10
  string constant INPUT = string.concat(Schemas.Amount, "&", Schemas.Recipient);
10
11
 
12
+ abstract contract TransferHook {
13
+ /// @notice Override to execute a single transfer record from the request pipeline.
14
+ /// Called once per bundled AMOUNT+RECIPIENT pair in the request.
15
+ /// @param value Decoded transfer record (from, to, asset, meta, amount).
16
+ function transfer(Tx memory value) internal virtual;
17
+ }
18
+
11
19
  /// @title Transfer
12
20
  /// @notice Command that transfers assets from a caller to recipients specified in
13
21
  /// bundled AMOUNT+RECIPIENT request blocks. Produces no state output.
14
- /// The virtual `transfer(from, input)` hook is called once per bundle.
15
- abstract contract Transfer is CommandBase {
22
+ /// The virtual `transfer(value)` hook is called once per bundle.
23
+ abstract contract Transfer is CommandBase, TransferHook {
16
24
  uint internal immutable transferId = commandId(NAME);
17
25
 
18
26
  constructor() {
19
27
  emit Command(host, NAME, INPUT, transferId, State.Empty, State.Empty);
20
28
  }
21
29
 
22
- /// @notice Override to execute a single transfer described by the current `input` position.
23
- /// Called once per bundled AMOUNT+RECIPIENT pair in the request.
24
- /// @param from Source account identifier.
25
- /// @param input Live request cursor positioned at the current bundle.
26
- function transfer(bytes32 from, Cur memory input) internal virtual;
27
-
28
30
  /// @notice Override to customize request parsing or batching for transfers.
29
- /// The default implementation iterates bundles and calls `transfer(from, input)` for each.
31
+ /// The default implementation iterates bundles and calls `transfer(value)` for each.
30
32
  /// @param from Source account identifier.
31
33
  /// @param request Full request bytes.
32
34
  /// @return Empty bytes (transfers produce no state output).
33
35
  function transfer(bytes32 from, bytes calldata request) internal virtual returns (bytes memory) {
34
36
  (Cur memory input, , ) = cursor(request, 1);
37
+ Tx memory value;
38
+ value.from = from;
35
39
 
36
40
  while (input.i < input.bound) {
37
- transfer(from, input);
41
+ Cur memory bundle = input.bundle();
42
+ (value.asset, value.meta, value.amount) = bundle.unpackAmount();
43
+ value.to = Accounts.ensure(bundle.unpackRecipient());
44
+ transfer(value);
38
45
  }
39
46
 
40
47
  input.complete();
@@ -3,15 +3,14 @@ pragma solidity ^0.8.33;
3
3
 
4
4
  import { EventEmitter } from "./Emitter.sol";
5
5
 
6
- string constant ABI = "event RootZero(uint indexed host, bytes32 account, uint deadline, uint value)";
6
+ string constant ABI = "event RootZero(bytes32 indexed account, uint deadline, uint value)";
7
7
 
8
8
  /// @notice Emitted for root-level protocol actions (e.g. governance or protocol-wide operations).
9
9
  abstract contract RootZeroEvent is EventEmitter {
10
- /// @param host Host node ID where the action occurred.
11
10
  /// @param account Account identifier associated with the action.
12
11
  /// @param deadline Expiry timestamp of the action.
13
12
  /// @param value Native value associated with the action.
14
- event RootZero(uint indexed host, bytes32 account, uint deadline, uint value);
13
+ event RootZero(bytes32 indexed account, uint deadline, uint value);
15
14
 
16
15
  constructor() {
17
16
  emit EventAbi(ABI);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rootzero/contracts",
3
- "version": "0.4.0",
3
+ "version": "0.5.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",
@@ -0,0 +1,34 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import { PeerBase } from "./Base.sol";
5
+ import { TransferHook } from "../commands/Transfer.sol";
6
+ import { Cursors, Cur, Tx, Schemas } from "../Cursors.sol";
7
+
8
+ using Cursors for Cur;
9
+
10
+ string constant NAME = "peerSettle";
11
+
12
+ /// @title PeerSettle
13
+ /// @notice Peer that consumes peer-supplied TRANSACTION blocks through the shared transfer hook.
14
+ /// Each TRANSACTION block in the request calls `transfer(value)`. Restricted to trusted peers.
15
+ abstract contract PeerSettle is PeerBase, TransferHook {
16
+ uint internal immutable peerSettleId = peerId(NAME);
17
+
18
+ constructor() {
19
+ emit Peer(host, NAME, Schemas.Transaction, peerSettleId);
20
+ }
21
+
22
+ /// @notice Execute the peer-settle call.
23
+ function peerSettle(bytes calldata request) external payable onlyPeer returns (bytes memory) {
24
+ (Cur memory state, , ) = cursor(request, 1);
25
+
26
+ while (state.i < state.bound) {
27
+ Tx memory value = state.unpackTxValue();
28
+ transfer(value);
29
+ }
30
+
31
+ state.complete();
32
+ return "";
33
+ }
34
+ }
@@ -70,6 +70,17 @@ library Accounts {
70
70
  return account == toKeccak(raw);
71
71
  }
72
72
 
73
+ /// @notice Assert that `account` uses the Account layout tag and return it unchanged.
74
+ /// Ignores width, chain binding, and subtype details.
75
+ /// @param account Account ID to validate.
76
+ /// @return The same `account` value if valid.
77
+ function ensure(bytes32 account) internal pure returns (bytes32) {
78
+ if (uint8(uint(account) >> 232) != Layout.Account) {
79
+ revert InvalidAccount();
80
+ }
81
+ return account;
82
+ }
83
+
73
84
  /// @notice Assert that `account` belongs to the EVM account family and return it unchanged.
74
85
  /// @param account Account ID to validate.
75
86
  /// @return The same `account` value if valid.
package/blocks/Mem.sol DELETED
@@ -1,188 +0,0 @@
1
- // SPDX-License-Identifier: GPL-3.0-only
2
- pragma solidity ^0.8.33;
3
-
4
- import { HostAmount, Tx, Keys } from "./Schema.sol";
5
- import { Cursors } from "./Cursors.sol";
6
-
7
- /// @notice Reference to a single block inside a `bytes memory` buffer.
8
- /// Positions are byte offsets within the buffer (not absolute memory addresses).
9
- struct MemRef {
10
- /// @dev Block type identifier read from the 4-byte header key.
11
- bytes4 key;
12
- /// @dev Payload start offset (byte immediately after the 8-byte header).
13
- uint i;
14
- /// @dev Payload end offset (equals `i + payloadLen`).
15
- uint end;
16
- }
17
-
18
- /// @title Mem
19
- /// @notice Memory block stream parser for the rootzero protocol.
20
- /// Mirrors the calldata-oriented `Cursors` API but operates on `bytes memory`
21
- /// buffers, using `mcopy`/`mload` assembly instead of `msg.data` slices.
22
- library Mem {
23
- /// @notice Parse a block header at offset `i` within `source`.
24
- /// Returns an empty sentinel `MemRef` (all zeros) when `i` is at end-of-data.
25
- /// @param source In-memory block stream buffer.
26
- /// @param i Byte offset of the block header within `source`.
27
- /// @return ref Parsed block reference with key, payload start, and payload end.
28
- function from(bytes memory source, uint i) internal pure returns (MemRef memory ref) {
29
- uint eod = source.length;
30
- if (i == eod) return MemRef(bytes4(0), i, i);
31
- if (i > eod) revert Cursors.MalformedBlocks();
32
-
33
- unchecked {
34
- ref.i = i + 8;
35
- }
36
- if (ref.i > eod) revert Cursors.MalformedBlocks();
37
-
38
- // Read the 8-byte block header (key + payloadLen) in one mload.
39
- bytes32 w;
40
- assembly ("memory-safe") {
41
- w := mload(add(add(source, 0x20), i))
42
- }
43
-
44
- ref.key = bytes4(w);
45
- ref.end = ref.i + uint32(bytes4(w << 32));
46
- if (ref.end > eod) revert Cursors.MalformedBlocks();
47
- }
48
-
49
- /// @notice Extract a byte range `[start, end)` from `source` into a new buffer.
50
- /// @param source Source buffer.
51
- /// @param start Inclusive start offset.
52
- /// @param end Exclusive end offset.
53
- /// @return out Copied slice as a new `bytes` value.
54
- function slice(bytes memory source, uint start, uint end) internal pure returns (bytes memory out) {
55
- if (end < start || end > source.length) revert Cursors.MalformedBlocks();
56
- uint len = end - start;
57
- out = new bytes(len);
58
- if (len == 0) return out;
59
-
60
- assembly ("memory-safe") {
61
- mcopy(add(out, 0x20), add(add(source, 0x20), start), len)
62
- }
63
- }
64
-
65
- /// @notice Count consecutive blocks of `key` starting at offset `i`.
66
- /// @param source Source buffer.
67
- /// @param i Starting byte offset.
68
- /// @param key Block type to count.
69
- /// @return total Number of consecutive matching blocks.
70
- /// @return cursor Byte offset immediately after the last counted block.
71
- function count(bytes memory source, uint i, bytes4 key) internal pure returns (uint total, uint cursor) {
72
- cursor = i;
73
- while (cursor < source.length) {
74
- MemRef memory ref = from(source, cursor);
75
- if (ref.key != key) break;
76
- unchecked {
77
- ++total;
78
- }
79
- cursor = ref.end;
80
- }
81
- }
82
-
83
- /// @notice Scan forward from `i` up to `limit` for the first block matching `key`.
84
- /// Returns an empty sentinel `MemRef` (key == 0, i == end == limit) if not found.
85
- /// @param source Source buffer.
86
- /// @param i Starting byte offset.
87
- /// @param limit Exclusive upper bound for the search (must be ≤ `source.length`).
88
- /// @param key Block type to find.
89
- /// @return ref Reference to the first matching block, or the sentinel if absent.
90
- function find(bytes memory source, uint i, uint limit, bytes4 key) internal pure returns (MemRef memory ref) {
91
- if (limit > source.length) revert Cursors.MalformedBlocks();
92
- while (i < limit) {
93
- ref = from(source, i);
94
- if (ref.end > limit) revert Cursors.MalformedBlocks();
95
- if (ref.key == key) return ref;
96
- i = ref.end;
97
- }
98
-
99
- return MemRef(bytes4(0), limit, limit);
100
- }
101
-
102
- /// @notice Assert that `ref` points to a block of the expected type.
103
- /// @param ref Block reference to validate.
104
- /// @param key Expected block type key.
105
- function ensure(MemRef memory ref, bytes4 key) internal pure {
106
- if (key == 0 || key != ref.key) revert Cursors.InvalidBlock();
107
- }
108
-
109
- /// @notice Assert that `ref` points to a block of the expected type with exact payload length.
110
- /// @param ref Block reference to validate.
111
- /// @param key Expected block type key.
112
- /// @param len Expected payload byte length.
113
- function ensure(MemRef memory ref, bytes4 key, uint len) internal pure {
114
- if (key == 0 || key != ref.key || len != (ref.end - ref.i)) revert Cursors.InvalidBlock();
115
- }
116
-
117
- /// @notice Assert that `ref` points to a block of the expected type within a length range.
118
- /// @param ref Block reference to validate.
119
- /// @param key Expected block type key.
120
- /// @param min Minimum payload length (inclusive).
121
- /// @param max Maximum payload length (inclusive); 0 means unbounded.
122
- function ensure(MemRef memory ref, bytes4 key, uint min, uint max) internal pure {
123
- uint len = ref.end - ref.i;
124
- if (key == 0 || key != ref.key || len < min || (max != 0 && len > max)) revert Cursors.InvalidBlock();
125
- }
126
-
127
- /// @notice Decode a BALANCE block payload from `source` using the given reference.
128
- /// @param ref Block reference; must point to a BALANCE block with exactly 96 payload bytes.
129
- /// @param source Buffer containing the block.
130
- /// @return asset Asset identifier.
131
- /// @return meta Asset metadata slot.
132
- /// @return amount Token amount.
133
- function unpackBalance(
134
- MemRef memory ref,
135
- bytes memory source
136
- ) internal pure returns (bytes32 asset, bytes32 meta, uint amount) {
137
- ensure(ref, Keys.Balance, 96);
138
- uint i = ref.i;
139
-
140
- // Read three contiguous 32-byte words from the payload.
141
- assembly ("memory-safe") {
142
- let p := add(add(source, 0x20), i)
143
- asset := mload(p)
144
- meta := mload(add(p, 0x20))
145
- amount := mload(add(p, 0x40))
146
- }
147
- }
148
-
149
- /// @notice Decode a CUSTODY block payload from `source` into a `HostAmount` struct.
150
- /// @param ref Block reference; must point to a CUSTODY block with exactly 128 payload bytes.
151
- /// @param source Buffer containing the block.
152
- /// @return value Decoded host, asset, meta, and amount.
153
- function toCustodyValue(
154
- MemRef memory ref,
155
- bytes memory source
156
- ) internal pure returns (HostAmount memory value) {
157
- ensure(ref, Keys.Custody, 128);
158
- uint i = ref.i;
159
-
160
- // Copy four 32-byte payload words directly into the struct memory slots.
161
- assembly ("memory-safe") {
162
- let p := add(add(source, 0x20), i)
163
- mstore(value, mload(p))
164
- mstore(add(value, 0x20), mload(add(p, 0x20)))
165
- mstore(add(value, 0x40), mload(add(p, 0x40)))
166
- mstore(add(value, 0x60), mload(add(p, 0x60)))
167
- }
168
- }
169
-
170
- /// @notice Decode a TRANSACTION block payload from `source` into a `Tx` struct.
171
- /// @param ref Block reference; must point to a TRANSACTION block with exactly 160 payload bytes.
172
- /// @param source Buffer containing the block.
173
- /// @return value Decoded from, to, asset, meta, and amount.
174
- function toTxValue(MemRef memory ref, bytes memory source) internal pure returns (Tx memory value) {
175
- ensure(ref, Keys.Transaction, 160);
176
- uint i = ref.i;
177
-
178
- // Copy five 32-byte payload words directly into the struct memory slots.
179
- assembly ("memory-safe") {
180
- let p := add(add(source, 0x20), i)
181
- mstore(value, mload(p))
182
- mstore(add(value, 0x20), mload(add(p, 0x20)))
183
- mstore(add(value, 0x40), mload(add(p, 0x40)))
184
- mstore(add(value, 0x60), mload(add(p, 0x60)))
185
- mstore(add(value, 0x80), mload(add(p, 0x80)))
186
- }
187
- }
188
- }