@rootzero/contracts 0.7.1 → 0.8.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.
package/Commands.sol CHANGED
@@ -6,33 +6,33 @@ pragma solidity ^0.8.33;
6
6
 
7
7
  import { CommandBase, CommandContext, CommandPayable, encodeCommandCall } from "./commands/Base.sol";
8
8
  import { State } from "./utils/State.sol";
9
- import { Burn } from "./commands/Burn.sol";
10
- import { Create } from "./commands/Create.sol";
11
- import { CreditAccount } from "./commands/Credit.sol";
12
- import { DebitAccount } from "./commands/Debit.sol";
13
- import { Deposit, DepositPayable } from "./commands/Deposit.sol";
14
- import { Remove } from "./commands/Remove.sol";
15
- import { PipePayable } from "./commands/Pipe.sol";
16
- import { Provision, ProvisionPayable, ProvisionFromBalance } from "./commands/Provision.sol";
9
+ import { Burn, BurnHook } from "./commands/Burn.sol";
10
+ import { Create, CreateHook } from "./commands/Create.sol";
11
+ import { CreditAccount, CreditAccountHook } from "./commands/Credit.sol";
12
+ import { DebitAccount, DebitAccountHook } from "./commands/Debit.sol";
13
+ import { Deposit, DepositHook, DepositPayable, DepositPayableHook } from "./commands/Deposit.sol";
14
+ import { Remove, RemoveHook } from "./commands/Remove.sol";
15
+ import { PipePayable, PipePayableHook } from "./commands/Pipe.sol";
16
+ import { Provision, ProvisionHook, ProvisionPayable, ProvisionPayableHook, ProvisionFromBalance } from "./commands/Provision.sol";
17
17
  import { Settle } from "./commands/Settle.sol";
18
- import { StakeCustodyToPosition } from "./commands/Stake.sol";
19
- import { Supply } from "./commands/Supply.sol";
20
- import { Transfer } from "./commands/Transfer.sol";
21
- import { Withdraw } from "./commands/Withdraw.sol";
18
+ import { StakeCustodyToPosition, StakeCustodyToPositionHook } from "./commands/Stake.sol";
19
+ import { Supply, SupplyHook } from "./commands/Supply.sol";
20
+ import { Transfer, TransferHook } from "./commands/Transfer.sol";
21
+ import { Withdraw, WithdrawHook } from "./commands/Withdraw.sol";
22
22
  import { AllowAssets, AllowAssetsHook } from "./commands/admin/AllowAssets.sol";
23
- import { Destroy } from "./commands/admin/Destroy.sol";
23
+ import { Destroy, DestroyHook } from "./commands/admin/Destroy.sol";
24
24
  import { Authorize } from "./commands/admin/Authorize.sol";
25
25
  import { DenyAssets, DenyAssetsHook } from "./commands/admin/DenyAssets.sol";
26
- import { Init } from "./commands/admin/Init.sol";
26
+ import { Init, InitHook } from "./commands/admin/Init.sol";
27
27
  import { RelocatePayable } from "./commands/admin/Relocate.sol";
28
- import { Allocate } from "./commands/admin/Allocate.sol";
28
+ import { Allocate, AllocateHook } from "./commands/admin/Allocate.sol";
29
29
  import { Unauthorize } from "./commands/admin/Unauthorize.sol";
30
30
  import { PeerBase, encodePeerCall } from "./peer/Base.sol";
31
- import { PeerAssetPull } from "./peer/AssetPull.sol";
31
+ import { PeerAssetPull, PeerAssetPullHook } from "./peer/AssetPull.sol";
32
32
  import { PeerAllowAssets } from "./peer/AllowAssets.sol";
33
33
  import { PeerDenyAssets } from "./peer/DenyAssets.sol";
34
- import { PeerPull } from "./peer/Pull.sol";
35
- import { PeerPush } from "./peer/Push.sol";
34
+ import { PeerPull, PeerPullHook } from "./peer/Pull.sol";
35
+ import { PeerPush, PeerPushHook } from "./peer/Push.sol";
36
36
  import { PeerSettle } from "./peer/Settle.sol";
37
37
 
38
38
 
package/Cursors.sol CHANGED
@@ -7,7 +7,10 @@ pragma solidity ^0.8.33;
7
7
  import { HostAmount, UserAmount, HostAsset, Tx, AssetAmount, Sizes } from "./blocks/Schema.sol";
8
8
  import { Keys } from "./blocks/Keys.sol";
9
9
  import { Schemas } from "./blocks/Schema.sol";
10
- import { Cursors, Cur } from "./blocks/Cursors.sol";
10
+ import { Cursors, Cur } from "./blocks/cursors/Core.sol";
11
+ import { Erc20Cursors } from "./blocks/cursors/Erc20.sol";
12
+ import { Erc1155Cursors } from "./blocks/cursors/Erc1155.sol";
13
+ import { Erc721Cursors } from "./blocks/cursors/Erc721.sol";
11
14
  import { Writer, Writers } from "./blocks/Writers.sol";
12
15
 
13
16
 
package/Queries.sol CHANGED
@@ -4,7 +4,7 @@ pragma solidity ^0.8.33;
4
4
  // Aggregator: re-exports query abstractions and reusable query bases.
5
5
  // Import this file to build rootzero query contracts without managing individual paths.
6
6
 
7
- import { IsAllowedAsset } from "./queries/Assets.sol";
8
- import { AssetPosition } from "./queries/Positions.sol";
9
- import { GetBalances } from "./queries/Balances.sol";
7
+ import { IsAllowedAsset, IsAllowedAssetHook } from "./queries/Assets.sol";
8
+ import { AssetPosition, AssetPositionHook } from "./queries/Positions.sol";
9
+ import { GetBalances, GetBalancesHook } from "./queries/Balances.sol";
10
10
  import { QueryBase, encodeQueryCall } from "./queries/Base.sol";
package/blocks/Keys.sol CHANGED
@@ -28,6 +28,7 @@ library Keys {
28
28
  bytes4 constant Break = bytes4(keccak256("break()"));
29
29
  /// @dev Bundle wrapper — (bytes data); payload is an embedded block stream
30
30
  bytes4 constant Bundle = bytes4(keccak256("bundle(bytes data)"));
31
+ bytes4 constant List = bytes4(keccak256("list(bytes data)"));
31
32
  /// @dev Extensible routing field — (bytes data); layout is command-defined
32
33
  bytes4 constant Route = bytes4(keccak256("route(bytes data)"));
33
34
  /// @dev Extensible query field - (bytes data); layout is query-defined, key is always `Keys.Query`
package/blocks/Schema.sol CHANGED
@@ -67,10 +67,13 @@ library Schemas {
67
67
  // Schema DSL:
68
68
  // - `;` separates top-level sibling blocks
69
69
  // - `&` bundles adjacent blocks into one bundle block
70
+ // - a bundle may optionally be named in the form `name = a & b`
71
+ // - postfix `[]` marks a repeated list in the simple suffix form, e.g. `asset(...)[]`
72
+ // - a list of bundled items must use an inline named form `name[] = a & b`
70
73
  // - bundled blocks preserve member order, so `a & b` differs from `b & a`
71
74
  // - a bundle block's self payload is an embedded normal block stream of its bundled members
72
75
  // - bundled members keep their ordinary block encoding, so dynamic blocks are allowed inside bundles
73
- // - `->` separates request and response shapes, appears at most once, and is omitted when no output is modeled
76
+ // - a list block's self payload is an embedded normal block stream representing the repeated items
74
77
  // - top-level blocks of the same type should be grouped together
75
78
  // - primary / driving blocks should appear before auxiliary blocks
76
79
  // - `route(<fields...>)`, `query(<fields...>)`, and `response(<fields...>)` are reserved
@@ -79,6 +82,10 @@ library Schemas {
79
82
  // - these extensible forms work like dynamic `bytes` blocks: they may carry arbitrary
80
83
  // payload bytes while keeping one fixed key per semantic block type
81
84
  // - `&` compiles to a `Keys.Bundle` block whose self payload is the bundled member block stream
85
+ // - `[]` compiles to a `Keys.List` block whose self payload is the repeated item block stream
86
+ // - `asset(...)[]` means a list whose repeated item is the block `asset(...)`
87
+ // - `steps[] = asset(...) & recipient(...)` means a named list whose repeated item is the bundle
88
+ // `asset(...) & recipient(...)`
82
89
  // - canonical blocks are `amount(...)` for request amounts, `balance(...)` for state balances,
83
90
  // `minimum(...)` for result floors, `maximum(...)` for spend ceilings, and `quantity(...)`
84
91
  // for plain scalar amounts
@@ -1,8 +1,8 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import {HostAsset, AssetAmount, HostAmount, Tx, Keys, Sizes} from "./Schema.sol";
5
- import {ALLOC_SCALE, Writer, Writers} from "./Writers.sol";
4
+ import {HostAsset, AssetAmount, HostAmount, Tx, Keys, Sizes} from "../Schema.sol";
5
+ import {ALLOC_SCALE, Writer, Writers} from "../Writers.sol";
6
6
 
7
7
  /// @notice Zero-copy view into a calldata block stream.
8
8
  /// All positions (`i`, `bound`) are byte offsets relative to the start of the source region.
@@ -74,6 +74,18 @@ library Cursors {
74
74
  return cur;
75
75
  }
76
76
 
77
+ /// @notice Create a subcursor over the half-open range `[from, to)` within the source region.
78
+ /// The returned cursor starts at position zero within that sliced region.
79
+ /// @param cur Source cursor.
80
+ /// @param from Start byte offset within the source region (inclusive).
81
+ /// @param to End byte offset within the source region (exclusive).
82
+ /// @return out Cursor scoped to the requested sub-range.
83
+ function slice(Cur memory cur, uint from, uint to) internal pure returns (Cur memory out) {
84
+ if (from > to || to > cur.len) revert MalformedBlocks();
85
+ out.offset = cur.offset + from;
86
+ out.len = to - from;
87
+ }
88
+
77
89
  /// @notice Read a block header at position `i` without advancing the cursor.
78
90
  /// @param cur Source cursor.
79
91
  /// @param i Byte offset of the block header within the source region.
@@ -141,7 +153,7 @@ library Cursors {
141
153
  /// @return quotient Number of groups represented by the run (`count / group`).
142
154
  function primeRun(Cur memory cur, uint group) internal pure returns (bytes4 key, uint count, uint quotient) {
143
155
  if (group == 0) revert ZeroGroup();
144
- key = cur.len < 4 ? bytes4(0) : bytes4(msg.data[cur.offset:cur.offset + 4]);
156
+ key = cur.i + 4 > cur.len ? bytes4(0) : bytes4(msg.data[cur.offset + cur.i:cur.offset + cur.i + 4]);
145
157
  (count, cur.bound) = countRun(cur, cur.i, key);
146
158
  if (count == 0) revert ZeroCursor();
147
159
  if (count % group != 0) revert BadRatio();
@@ -182,16 +194,48 @@ library Cursors {
182
194
  cur.i = next;
183
195
  }
184
196
 
185
- /// @notice Parse a Bundle block at the current position and return an inner cursor.
186
- /// Advances `cur.i` past the bundle block.
187
- /// @param cur Outer cursor; advanced by one bundle block.
188
- /// @return out Inner cursor scoped to the bundle's embedded block stream.
189
- function bundle(Cur memory cur) internal pure returns (Cur memory out) {
190
- (uint abs, uint next) = expect(cur, cur.i, Keys.Bundle, 0, 0);
191
- uint len = next - (abs - cur.offset);
192
- out.offset = abs;
193
- out.len = len;
194
- cur.i = next;
197
+ /// @notice Enter a Bundle block at the current position and return the next offset.
198
+ /// Advances `cur.i` past the bundle header so the bundled members can be parsed
199
+ /// directly from the same cursor. The returned `next` is the byte offset
200
+ /// immediately after the bundle payload, relative to the current cursor region.
201
+ /// @param cur Cursor positioned at a bundle block; advanced past the 8-byte header.
202
+ /// @return next Byte offset immediately after the bundle payload.
203
+ function bundle(Cur memory cur) internal pure returns (uint next) {
204
+ (, next) = expect(cur, cur.i, Keys.Bundle, 0, 0);
205
+ cur.i += 8;
206
+ }
207
+
208
+ /// @notice Enter a List block at the current position and return the next offset.
209
+ /// Advances `cur.i` past the list header so the list members can be parsed
210
+ /// directly from the same cursor. The returned `next` is the byte offset
211
+ /// immediately after the list payload, relative to the current cursor region.
212
+ /// @param cur Cursor positioned at a list block; advanced past the 8-byte header.
213
+ /// @return next Byte offset immediately after the list payload.
214
+ function list(Cur memory cur) internal pure returns (uint next) {
215
+ (, next) = expect(cur, cur.i, Keys.List, 0, 0);
216
+ cur.i += 8;
217
+ }
218
+
219
+ /// @notice Enter a List block, prime its member run, and return the raw block count.
220
+ /// @param cur Cursor positioned at a list block; advanced past the 8-byte header.
221
+ /// @param group Expected block group size for the list item stream.
222
+ /// @return count Total number of blocks in the list payload (a multiple of `group`).
223
+ /// @return next Byte offset immediately after the list payload.
224
+ function list(Cur memory cur, uint group) internal pure returns (uint count, uint next) {
225
+ next = list(cur);
226
+ (, count, ) = cur.primeRun(group);
227
+ if (cur.bound != next) revert IncompleteCursor();
228
+ }
229
+
230
+ /// @notice Enter a List block, prime its member run, and require an exact raw block count.
231
+ /// @param cur Cursor positioned at a list block; advanced past the 8-byte header.
232
+ /// @param group Expected block group size for the list item stream.
233
+ /// @param requiredCount Required number of blocks in the list payload.
234
+ /// @return next Byte offset immediately after the list payload.
235
+ function list(Cur memory cur, uint group, uint requiredCount) internal pure returns (uint next) {
236
+ uint count;
237
+ (count, next) = list(cur, group);
238
+ if (count != requiredCount) revert BadRatio();
195
239
  }
196
240
 
197
241
  /// @notice Assert that the cursor has consumed exactly up to `bound`.
@@ -201,6 +245,32 @@ library Cursors {
201
245
  if (cur.bound == 0 || cur.i != cur.bound) revert IncompleteCursor();
202
246
  }
203
247
 
248
+ /// @notice Assert that the cursor has consumed its entire source region.
249
+ /// Reverts with `IncompleteCursor` when `cur.i != cur.len`.
250
+ /// @param cur Cursor to check.
251
+ function end(Cur memory cur) internal pure {
252
+ if (cur.i != cur.len) revert IncompleteCursor();
253
+ }
254
+
255
+ /// @notice Resume parsing after a nested region delimited by `resumeAt`.
256
+ /// Reverts with `IncompleteCursor` if `cur.i` has advanced past `resumeAt` or `resumeAt`
257
+ /// exceeds the cursor region length. Otherwise moves `cur.i` to `end`.
258
+ /// @param cur Cursor to advance.
259
+ /// @param resumeAt Relative end offset of the nested region to resume after.
260
+ function resume(Cur memory cur, uint resumeAt) internal pure {
261
+ if (resumeAt > cur.len || cur.i > resumeAt) revert IncompleteCursor();
262
+ cur.i = resumeAt;
263
+ }
264
+
265
+ /// @notice Ensure that parsing has reached an exact nested-region boundary.
266
+ /// Reverts with `IncompleteCursor` if `ensureAt` exceeds the cursor region length
267
+ /// or `cur.i != ensureAt`.
268
+ /// @param cur Cursor to check.
269
+ /// @param ensureAt Relative offset that `cur.i` must match exactly.
270
+ function ensure(Cur memory cur, uint ensureAt) internal pure {
271
+ if (ensureAt > cur.len || cur.i != ensureAt) revert IncompleteCursor();
272
+ }
273
+
204
274
  /// @notice Assert completion and finalise a writer in one step.
205
275
  /// @param cur Cursor to check.
206
276
  /// @param writer Writer to finalise.
@@ -0,0 +1,149 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import { Cur, Cursors } from "./Core.sol";
5
+ import { Keys } from "../Keys.sol";
6
+ import { Assets } from "../../utils/Assets.sol";
7
+
8
+ using Assets for bytes32;
9
+
10
+ /// @title Erc1155Cursors
11
+ /// @notice ERC-1155-aware cursor helpers layered on top of generic block parsing.
12
+ library Erc1155Cursors {
13
+ /// @notice Validate an AMOUNT block for a specific local ERC-1155 collection and return its token id and amount.
14
+ /// Reverts if the block is not AMOUNT, the asset is not a local ERC-1155, or the collection differs.
15
+ /// @param cur Source cursor.
16
+ /// @param i Byte offset of the AMOUNT block.
17
+ /// @param collection Expected local ERC-1155 collection.
18
+ /// @return meta Asset metadata slot from the block.
19
+ /// @return amount Amount from the block.
20
+ function expectErc1155Amount(Cur memory cur, uint i, address collection) internal view returns (bytes32 meta, uint amount) {
21
+ (uint abs, ) = Cursors.expect(cur, i, Keys.Amount, 96, 96);
22
+ bytes32(msg.data[abs:abs + 32]).matchErc1155(collection);
23
+ meta = bytes32(msg.data[abs + 32:abs + 64]);
24
+ amount = uint(bytes32(msg.data[abs + 64:abs + 96]));
25
+ }
26
+
27
+ /// @notice Validate a BALANCE block for a specific local ERC-1155 collection and return its token id and amount.
28
+ /// Reverts if the block is not BALANCE, the asset is not a local ERC-1155, or the collection differs.
29
+ /// @param cur Source cursor.
30
+ /// @param i Byte offset of the BALANCE block.
31
+ /// @param collection Expected local ERC-1155 collection.
32
+ /// @return meta Asset metadata slot from the block.
33
+ /// @return amount Balance amount from the block.
34
+ function expectErc1155Balance(Cur memory cur, uint i, address collection) internal view returns (bytes32 meta, uint amount) {
35
+ (uint abs, ) = Cursors.expect(cur, i, Keys.Balance, 96, 96);
36
+ bytes32(msg.data[abs:abs + 32]).matchErc1155(collection);
37
+ meta = bytes32(msg.data[abs + 32:abs + 64]);
38
+ amount = uint(bytes32(msg.data[abs + 64:abs + 96]));
39
+ }
40
+
41
+ /// @notice Validate a MINIMUM block for a specific local ERC-1155 collection and return its token id and amount.
42
+ /// Reverts if the block is not MINIMUM, the asset is not a local ERC-1155, or the collection differs.
43
+ /// @param cur Source cursor.
44
+ /// @param i Byte offset of the MINIMUM block.
45
+ /// @param collection Expected local ERC-1155 collection.
46
+ /// @return meta Asset metadata slot from the block.
47
+ /// @return amount Minimum amount from the block.
48
+ function expectErc1155Minimum(Cur memory cur, uint i, address collection) internal view returns (bytes32 meta, uint amount) {
49
+ (uint abs, ) = Cursors.expect(cur, i, Keys.Minimum, 96, 96);
50
+ bytes32(msg.data[abs:abs + 32]).matchErc1155(collection);
51
+ meta = bytes32(msg.data[abs + 32:abs + 64]);
52
+ amount = uint(bytes32(msg.data[abs + 64:abs + 96]));
53
+ }
54
+
55
+ /// @notice Validate a MAXIMUM block for a specific local ERC-1155 collection and return its token id and amount.
56
+ /// Reverts if the block is not MAXIMUM, the asset is not a local ERC-1155, or the collection differs.
57
+ /// @param cur Source cursor.
58
+ /// @param i Byte offset of the MAXIMUM block.
59
+ /// @param collection Expected local ERC-1155 collection.
60
+ /// @return meta Asset metadata slot from the block.
61
+ /// @return amount Maximum amount from the block.
62
+ function expectErc1155Maximum(Cur memory cur, uint i, address collection) internal view returns (bytes32 meta, uint amount) {
63
+ (uint abs, ) = Cursors.expect(cur, i, Keys.Maximum, 96, 96);
64
+ bytes32(msg.data[abs:abs + 32]).matchErc1155(collection);
65
+ meta = bytes32(msg.data[abs + 32:abs + 64]);
66
+ amount = uint(bytes32(msg.data[abs + 64:abs + 96]));
67
+ }
68
+
69
+ /// @notice Validate a CUSTODY block for a specific local ERC-1155 collection and return its token id and amount.
70
+ /// Reverts if the block is not CUSTODY, the host differs, the asset is not a local ERC-1155, or the collection differs.
71
+ /// @param cur Source cursor.
72
+ /// @param i Byte offset of the CUSTODY block.
73
+ /// @param host Expected host node ID from the block.
74
+ /// @param collection Expected local ERC-1155 collection.
75
+ /// @return meta Asset metadata slot from the block.
76
+ /// @return amount Custodied amount from the block.
77
+ function expectErc1155Custody(
78
+ Cur memory cur,
79
+ uint i,
80
+ uint host,
81
+ address collection
82
+ ) internal view returns (bytes32 meta, uint amount) {
83
+ (uint abs, ) = Cursors.expect(cur, i, Keys.Custody, 128, 128);
84
+ if (uint(bytes32(msg.data[abs:abs + 32])) != host) revert Cursors.UnexpectedValue();
85
+ bytes32(msg.data[abs + 32:abs + 64]).matchErc1155(collection);
86
+ meta = bytes32(msg.data[abs + 64:abs + 96]);
87
+ amount = uint(bytes32(msg.data[abs + 96:abs + 128]));
88
+ }
89
+
90
+ /// @notice Consume an AMOUNT block for a specific local ERC-1155 collection and return its token id and amount.
91
+ /// Reverts if the current block is not AMOUNT, the asset is not a local ERC-1155, or the collection differs.
92
+ /// @param cur Cursor; advanced past the block.
93
+ /// @param collection Expected local ERC-1155 collection.
94
+ /// @return meta Asset metadata slot from the block.
95
+ /// @return amount Amount from the block.
96
+ function requireErc1155Amount(Cur memory cur, address collection) internal view returns (bytes32 meta, uint amount) {
97
+ (meta, amount) = expectErc1155Amount(cur, cur.i, collection);
98
+ cur.i += 104;
99
+ }
100
+
101
+ /// @notice Consume a BALANCE block for a specific local ERC-1155 collection and return its token id and amount.
102
+ /// Reverts if the current block is not BALANCE, the asset is not a local ERC-1155, or the collection differs.
103
+ /// @param cur Cursor; advanced past the block.
104
+ /// @param collection Expected local ERC-1155 collection.
105
+ /// @return meta Asset metadata slot from the block.
106
+ /// @return amount Balance amount from the block.
107
+ function requireErc1155Balance(Cur memory cur, address collection) internal view returns (bytes32 meta, uint amount) {
108
+ (meta, amount) = expectErc1155Balance(cur, cur.i, collection);
109
+ cur.i += 104;
110
+ }
111
+
112
+ /// @notice Consume a MINIMUM block for a specific local ERC-1155 collection and return its token id and amount.
113
+ /// Reverts if the current block is not MINIMUM, the asset is not a local ERC-1155, or the collection differs.
114
+ /// @param cur Cursor; advanced past the block.
115
+ /// @param collection Expected local ERC-1155 collection.
116
+ /// @return meta Asset metadata slot from the block.
117
+ /// @return amount Minimum amount from the block.
118
+ function requireErc1155Minimum(Cur memory cur, address collection) internal view returns (bytes32 meta, uint amount) {
119
+ (meta, amount) = expectErc1155Minimum(cur, cur.i, collection);
120
+ cur.i += 104;
121
+ }
122
+
123
+ /// @notice Consume a MAXIMUM block for a specific local ERC-1155 collection and return its token id and amount.
124
+ /// Reverts if the current block is not MAXIMUM, the asset is not a local ERC-1155, or the collection differs.
125
+ /// @param cur Cursor; advanced past the block.
126
+ /// @param collection Expected local ERC-1155 collection.
127
+ /// @return meta Asset metadata slot from the block.
128
+ /// @return amount Maximum amount from the block.
129
+ function requireErc1155Maximum(Cur memory cur, address collection) internal view returns (bytes32 meta, uint amount) {
130
+ (meta, amount) = expectErc1155Maximum(cur, cur.i, collection);
131
+ cur.i += 104;
132
+ }
133
+
134
+ /// @notice Consume a CUSTODY block for a specific local ERC-1155 collection and return its token id and amount.
135
+ /// Reverts if the current block is not CUSTODY, the host differs, the asset is not a local ERC-1155, or the collection differs.
136
+ /// @param cur Cursor; advanced past the block.
137
+ /// @param host Expected host node ID from the block.
138
+ /// @param collection Expected local ERC-1155 collection.
139
+ /// @return meta Asset metadata slot from the block.
140
+ /// @return amount Custodied amount from the block.
141
+ function requireErc1155Custody(
142
+ Cur memory cur,
143
+ uint host,
144
+ address collection
145
+ ) internal view returns (bytes32 meta, uint amount) {
146
+ (meta, amount) = expectErc1155Custody(cur, cur.i, host, collection);
147
+ cur.i += 136;
148
+ }
149
+ }
@@ -0,0 +1,130 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import { Cur, Cursors } from "./Core.sol";
5
+ import { Keys } from "../Keys.sol";
6
+ import { Assets } from "../../utils/Assets.sol";
7
+
8
+ using Assets for bytes32;
9
+
10
+ /// @title Erc20Cursors
11
+ /// @notice ERC-20-aware cursor helpers layered on top of generic block parsing.
12
+ library Erc20Cursors {
13
+ function erc20AddrAt(uint abs) private view returns (address) {
14
+ return Assets.erc20Addr(bytes32(msg.data[abs:abs + 32]));
15
+ }
16
+
17
+ /// @notice Validate an AMOUNT block for any local ERC-20 token and return it.
18
+ /// Reverts if the block is not AMOUNT or the asset is not a local ERC-20.
19
+ /// @param cur Source cursor.
20
+ /// @param i Byte offset of the AMOUNT block.
21
+ /// @return token Local ERC-20 token address extracted from the asset identifier.
22
+ /// @return amount Amount from the block.
23
+ function expectErc20Amount(Cur memory cur, uint i) internal view returns (address token, uint amount) {
24
+ (uint abs, ) = Cursors.expect(cur, i, Keys.Amount, 96, 96);
25
+ token = erc20AddrAt(abs);
26
+ amount = uint(bytes32(msg.data[abs + 64:abs + 96]));
27
+ }
28
+
29
+ /// @notice Validate a BALANCE block for any local ERC-20 token and return it.
30
+ /// Reverts if the block is not BALANCE or the asset is not a local ERC-20.
31
+ /// @param cur Source cursor.
32
+ /// @param i Byte offset of the BALANCE block.
33
+ /// @return token Local ERC-20 token address extracted from the asset identifier.
34
+ /// @return amount Balance amount from the block.
35
+ function expectErc20Balance(Cur memory cur, uint i) internal view returns (address token, uint amount) {
36
+ (uint abs, ) = Cursors.expect(cur, i, Keys.Balance, 96, 96);
37
+ token = erc20AddrAt(abs);
38
+ amount = uint(bytes32(msg.data[abs + 64:abs + 96]));
39
+ }
40
+
41
+ /// @notice Validate a MINIMUM block for any local ERC-20 token and return it.
42
+ /// Reverts if the block is not MINIMUM or the asset is not a local ERC-20.
43
+ /// @param cur Source cursor.
44
+ /// @param i Byte offset of the MINIMUM block.
45
+ /// @return token Local ERC-20 token address extracted from the asset identifier.
46
+ /// @return amount Minimum amount from the block.
47
+ function expectErc20Minimum(Cur memory cur, uint i) internal view returns (address token, uint amount) {
48
+ (uint abs, ) = Cursors.expect(cur, i, Keys.Minimum, 96, 96);
49
+ token = erc20AddrAt(abs);
50
+ amount = uint(bytes32(msg.data[abs + 64:abs + 96]));
51
+ }
52
+
53
+ /// @notice Consume a MINIMUM block for any local ERC-20 token and return it.
54
+ /// Reverts if the current block is not MINIMUM or the asset is not a local ERC-20.
55
+ /// @param cur Cursor; advanced past the block.
56
+ /// @return token Local ERC-20 token address extracted from the asset identifier.
57
+ /// @return amount Minimum amount from the block.
58
+ function requireErc20Minimum(Cur memory cur) internal view returns (address token, uint amount) {
59
+ (token, amount) = expectErc20Minimum(cur, cur.i);
60
+ cur.i += 104;
61
+ }
62
+
63
+ /// @notice Validate a MAXIMUM block for any local ERC-20 token and return it.
64
+ /// Reverts if the block is not MAXIMUM or the asset is not a local ERC-20.
65
+ /// @param cur Source cursor.
66
+ /// @param i Byte offset of the MAXIMUM block.
67
+ /// @return token Local ERC-20 token address extracted from the asset identifier.
68
+ /// @return amount Maximum amount from the block.
69
+ function expectErc20Maximum(Cur memory cur, uint i) internal view returns (address token, uint amount) {
70
+ (uint abs, ) = Cursors.expect(cur, i, Keys.Maximum, 96, 96);
71
+ token = erc20AddrAt(abs);
72
+ amount = uint(bytes32(msg.data[abs + 64:abs + 96]));
73
+ }
74
+
75
+ /// @notice Validate a CUSTODY block for any local ERC-20 token and return it.
76
+ /// Reverts if the block is not CUSTODY or the asset is not a local ERC-20.
77
+ /// @param cur Source cursor.
78
+ /// @param i Byte offset of the CUSTODY block.
79
+ /// @param host Expected host node ID from the block.
80
+ /// @return token Local ERC-20 token address extracted from the asset identifier.
81
+ /// @return amount Custodied amount from the block.
82
+ function expectErc20Custody(Cur memory cur, uint i, uint host) internal view returns (address token, uint amount) {
83
+ (uint abs, ) = Cursors.expect(cur, i, Keys.Custody, 128, 128);
84
+ if (uint(bytes32(msg.data[abs:abs + 32])) != host) revert Cursors.UnexpectedValue();
85
+ token = erc20AddrAt(abs + 32);
86
+ amount = uint(bytes32(msg.data[abs + 96:abs + 128]));
87
+ }
88
+
89
+ /// @notice Consume an AMOUNT block for any local ERC-20 token and return it.
90
+ /// Reverts if the current block is not AMOUNT or the asset is not a local ERC-20.
91
+ /// @param cur Cursor; advanced past the block.
92
+ /// @return token Local ERC-20 token address extracted from the asset identifier.
93
+ /// @return amount Amount from the block.
94
+ function requireErc20Amount(Cur memory cur) internal view returns (address token, uint amount) {
95
+ (token, amount) = expectErc20Amount(cur, cur.i);
96
+ cur.i += 104;
97
+ }
98
+
99
+ /// @notice Consume a BALANCE block for any local ERC-20 token and return it.
100
+ /// Reverts if the current block is not BALANCE or the asset is not a local ERC-20.
101
+ /// @param cur Cursor; advanced past the block.
102
+ /// @return token Local ERC-20 token address extracted from the asset identifier.
103
+ /// @return amount Balance amount from the block.
104
+ function requireErc20Balance(Cur memory cur) internal view returns (address token, uint amount) {
105
+ (token, amount) = expectErc20Balance(cur, cur.i);
106
+ cur.i += 104;
107
+ }
108
+
109
+ /// @notice Consume a MAXIMUM block for any local ERC-20 token and return it.
110
+ /// Reverts if the current block is not MAXIMUM or the asset is not a local ERC-20.
111
+ /// @param cur Cursor; advanced past the block.
112
+ /// @return token Local ERC-20 token address extracted from the asset identifier.
113
+ /// @return amount Maximum amount from the block.
114
+ function requireErc20Maximum(Cur memory cur) internal view returns (address token, uint amount) {
115
+ (token, amount) = expectErc20Maximum(cur, cur.i);
116
+ cur.i += 104;
117
+ }
118
+
119
+ /// @notice Consume a CUSTODY block for any local ERC-20 token and return it.
120
+ /// Reverts if the current block is not CUSTODY or the asset is not a local ERC-20.
121
+ /// @param cur Cursor; advanced past the block.
122
+ /// @param host Expected host node ID from the block.
123
+ /// @return token Local ERC-20 token address extracted from the asset identifier.
124
+ /// @return amount Custodied amount from the block.
125
+ function requireErc20Custody(Cur memory cur, uint host) internal view returns (address token, uint amount) {
126
+ (token, amount) = expectErc20Custody(cur, cur.i, host);
127
+ cur.i += 136;
128
+ }
129
+
130
+ }
@@ -0,0 +1,66 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import { Cur, Cursors } from "./Core.sol";
5
+ import { Keys } from "../Keys.sol";
6
+ import { Assets } from "../../utils/Assets.sol";
7
+
8
+ using Assets for bytes32;
9
+
10
+ /// @title Erc721Cursors
11
+ /// @notice ERC-721-aware cursor helpers layered on top of generic block parsing.
12
+ library Erc721Cursors {
13
+ /// @notice Validate a BALANCE block for a specific local ERC-721 collection and return its metadata.
14
+ /// Reverts if the block is not BALANCE, the asset is not a local ERC-721, the collection differs, or the amount is not 1.
15
+ /// @param cur Source cursor.
16
+ /// @param i Byte offset of the BALANCE block.
17
+ /// @param collection Expected local ERC-721 collection.
18
+ /// @return meta Asset metadata slot from the block.
19
+ function expectErc721Balance(Cur memory cur, uint i, address collection) internal view returns (bytes32 meta) {
20
+ (uint abs, ) = Cursors.expect(cur, i, Keys.Balance, 96, 96);
21
+ bytes32(msg.data[abs:abs + 32]).matchErc721(collection);
22
+ meta = bytes32(msg.data[abs + 32:abs + 64]);
23
+ if (uint(bytes32(msg.data[abs + 64:abs + 96])) != 1) revert Cursors.UnexpectedValue();
24
+ }
25
+
26
+ /// @notice Validate a CUSTODY block for a specific local ERC-721 collection and return its metadata.
27
+ /// Reverts if the block is not CUSTODY, the host differs, the asset is not a local ERC-721, the collection differs, or the amount is not 1.
28
+ /// @param cur Source cursor.
29
+ /// @param i Byte offset of the CUSTODY block.
30
+ /// @param host Expected host node ID from the block.
31
+ /// @param collection Expected local ERC-721 collection.
32
+ /// @return meta Asset metadata slot from the block.
33
+ function expectErc721Custody(
34
+ Cur memory cur,
35
+ uint i,
36
+ uint host,
37
+ address collection
38
+ ) internal view returns (bytes32 meta) {
39
+ (uint abs, ) = Cursors.expect(cur, i, Keys.Custody, 128, 128);
40
+ if (uint(bytes32(msg.data[abs:abs + 32])) != host) revert Cursors.UnexpectedValue();
41
+ bytes32(msg.data[abs + 32:abs + 64]).matchErc721(collection);
42
+ meta = bytes32(msg.data[abs + 64:abs + 96]);
43
+ if (uint(bytes32(msg.data[abs + 96:abs + 128])) != 1) revert Cursors.UnexpectedValue();
44
+ }
45
+
46
+ /// @notice Consume a BALANCE block for a specific local ERC-721 collection and return its metadata.
47
+ /// Reverts if the current block is not BALANCE, the asset is not a local ERC-721, the collection differs, or the amount is not 1.
48
+ /// @param cur Cursor; advanced past the block.
49
+ /// @param collection Expected local ERC-721 collection.
50
+ /// @return meta Asset metadata slot from the block.
51
+ function requireErc721Balance(Cur memory cur, address collection) internal view returns (bytes32 meta) {
52
+ meta = expectErc721Balance(cur, cur.i, collection);
53
+ cur.i += 104;
54
+ }
55
+
56
+ /// @notice Consume a CUSTODY block for a specific local ERC-721 collection and return its metadata.
57
+ /// Reverts if the current block is not CUSTODY, the host differs, the asset is not a local ERC-721, the collection differs, or the amount is not 1.
58
+ /// @param cur Cursor; advanced past the block.
59
+ /// @param host Expected host node ID from the block.
60
+ /// @param collection Expected local ERC-721 collection.
61
+ /// @return meta Asset metadata slot from the block.
62
+ function requireErc721Custody(Cur memory cur, uint host, address collection) internal view returns (bytes32 meta) {
63
+ meta = expectErc721Custody(cur, cur.i, host, collection);
64
+ cur.i += 136;
65
+ }
66
+ }
package/commands/Burn.sol CHANGED
@@ -7,16 +7,7 @@ using Cursors for Cur;
7
7
 
8
8
  string constant NAME = "burn";
9
9
 
10
- /// @title Burn
11
- /// @notice Command that irreversibly destroys each BALANCE state block via a virtual hook.
12
- /// Produces no output state.
13
- abstract contract Burn is CommandBase {
14
- uint internal immutable burnId = commandId(NAME);
15
-
16
- constructor() {
17
- emit Command(host, NAME, "", burnId, State.Balances, State.Empty, false);
18
- }
19
-
10
+ abstract contract BurnHook {
20
11
  /// @notice Override to burn or consume the provided balance amount.
21
12
  /// Called once per BALANCE block in state.
22
13
  /// @param account Caller's account identifier.
@@ -25,6 +16,17 @@ abstract contract Burn is CommandBase {
25
16
  /// @param amount Amount to burn.
26
17
  /// @return Amount actually burned (may differ from `amount` for partial burns).
27
18
  function burn(bytes32 account, bytes32 asset, bytes32 meta, uint amount) internal virtual returns (uint);
19
+ }
20
+
21
+ /// @title Burn
22
+ /// @notice Command that irreversibly destroys each BALANCE state block via a virtual hook.
23
+ /// Produces no output state.
24
+ abstract contract Burn is CommandBase, BurnHook {
25
+ uint internal immutable burnId = commandId(NAME);
26
+
27
+ constructor() {
28
+ emit Command(host, NAME, "", burnId, State.Balances, State.Empty, false);
29
+ }
28
30
 
29
31
  function burn(CommandContext calldata c) external onlyCommand(burnId, c.target) returns (bytes memory) {
30
32
  (Cur memory state, , ) = cursor(c.state, 1);