@rootzero/contracts 0.7.2 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/Commands.sol +15 -20
  2. package/Core.sol +3 -4
  3. package/Cursors.sol +3 -2
  4. package/Events.sol +1 -1
  5. package/Queries.sol +3 -3
  6. package/README.md +18 -19
  7. package/Utils.sol +3 -3
  8. package/blocks/Cursors.sol +937 -551
  9. package/blocks/Keys.sol +60 -34
  10. package/blocks/Schema.sol +112 -122
  11. package/blocks/Writers.sol +476 -301
  12. package/commands/Base.sol +32 -22
  13. package/commands/Burn.sol +14 -12
  14. package/commands/Credit.sol +16 -15
  15. package/commands/Debit.sol +14 -12
  16. package/commands/Deposit.sol +30 -37
  17. package/commands/Pipe.sol +14 -20
  18. package/commands/Provision.sol +19 -49
  19. package/commands/Transfer.sol +9 -18
  20. package/commands/Withdraw.sol +15 -14
  21. package/commands/admin/AllowAssets.sol +3 -3
  22. package/commands/admin/Allowance.sol +43 -0
  23. package/commands/admin/Authorize.sol +4 -4
  24. package/commands/admin/DenyAssets.sol +3 -3
  25. package/commands/admin/Destroy.sol +10 -8
  26. package/commands/admin/Execute.sol +38 -0
  27. package/commands/admin/Init.sol +10 -8
  28. package/commands/admin/Relocate.sol +5 -5
  29. package/commands/admin/Unauthorize.sol +4 -4
  30. package/core/Access.sol +38 -34
  31. package/core/Balances.sol +17 -18
  32. package/core/{Operation.sol → Calls.sol} +5 -8
  33. package/core/{CursorBase.sol → Context.sol} +11 -5
  34. package/core/Host.sol +11 -10
  35. package/core/Types.sol +86 -0
  36. package/docs/GETTING_STARTED.md +37 -29
  37. package/events/Asset.sol +1 -1
  38. package/events/Command.sol +10 -10
  39. package/events/Deposit.sol +3 -4
  40. package/events/Listing.sol +1 -1
  41. package/events/Peer.sol +3 -3
  42. package/events/Position.sol +21 -0
  43. package/events/Query.sol +3 -3
  44. package/events/Withdraw.sol +2 -3
  45. package/package.json +1 -1
  46. package/peer/AllowAssets.sol +1 -1
  47. package/peer/Allowance.sol +36 -0
  48. package/peer/AssetPull.sol +33 -31
  49. package/peer/Base.sol +8 -4
  50. package/peer/DenyAssets.sol +1 -1
  51. package/peer/Settle.sol +3 -4
  52. package/queries/Assets.sol +18 -16
  53. package/queries/Balances.sol +21 -19
  54. package/queries/Base.sol +2 -3
  55. package/queries/Positions.sol +32 -24
  56. package/utils/Accounts.sol +14 -13
  57. package/utils/Assets.sol +137 -62
  58. package/utils/Ids.sol +9 -9
  59. package/utils/Layout.sol +5 -3
  60. package/utils/Utils.sol +10 -0
  61. package/commands/Create.sol +0 -42
  62. package/commands/Remove.sol +0 -42
  63. package/commands/Settle.sol +0 -38
  64. package/commands/Stake.sol +0 -47
  65. package/commands/Supply.sol +0 -41
  66. package/commands/admin/Allocate.sol +0 -41
  67. package/core/HostBound.sol +0 -14
  68. package/events/Erc721.sol +0 -20
  69. package/peer/Pull.sol +0 -39
  70. package/peer/Push.sol +0 -45
  71. package/utils/State.sol +0 -22
@@ -1,44 +1,46 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import {Cur, Cursors, Writer, Writers, Keys} from "../Cursors.sol";
5
- import {Schemas} from "../blocks/Schema.sol";
4
+ import {Cur, Cursors, Writer, Writers} from "../Cursors.sol";
5
+ import {Forms, Schemas} from "../blocks/Schema.sol";
6
6
  import {QueryBase} from "./Base.sol";
7
7
 
8
8
  using Cursors for Cur;
9
+ using Writers for Writer;
9
10
 
10
11
  string constant NAME = "isAllowedAsset";
11
- string constant OUTPUT = "response(uint allowed)";
12
+
13
+ abstract contract IsAllowedAssetHook {
14
+ /// @notice Resolve whether one asset tuple is allowed.
15
+ /// Concrete implementations define the allowlist policy.
16
+ /// @param asset Requested asset identifier.
17
+ /// @param meta Requested asset metadata slot.
18
+ /// @return allowed Whether the asset tuple is allowed.
19
+ function isAllowedAsset(bytes32 asset, bytes32 meta) internal view virtual returns (bool allowed);
20
+ }
12
21
 
13
22
  /// @title IsAllowedAsset
14
23
  /// @notice Rootzero query that checks whether one or more `(asset, meta)` tuples are allowed.
15
24
  /// The request is a run of `ASSET` blocks.
16
- /// The response returns one `RESPONSE` block per query entry, preserving request order.
17
- abstract contract IsAllowedAsset is QueryBase {
25
+ /// The response returns one `STATUS` form block per query entry, preserving request order.
26
+ abstract contract IsAllowedAsset is QueryBase, IsAllowedAssetHook {
18
27
  uint public immutable isAllowedAssetId = queryId(NAME);
19
28
 
20
29
  constructor() {
21
- emit Query(host, NAME, Schemas.Asset, OUTPUT, isAllowedAssetId);
30
+ emit Query(host, isAllowedAssetId, NAME, Schemas.Asset, Forms.Status);
22
31
  }
23
32
 
24
- /// @notice Resolve whether one asset tuple is allowed.
25
- /// Concrete implementations define the allowlist policy.
26
- /// @param asset Requested asset identifier.
27
- /// @param meta Requested asset metadata slot.
28
- /// @return allowed Whether the asset tuple is allowed.
29
- function isAllowedAsset(bytes32 asset, bytes32 meta) internal view virtual returns (bool allowed);
30
-
31
33
  /// @notice Resolve allowlist status for a run of requested `(asset, meta)` tuples.
32
34
  /// @param request Block-stream request consisting of `asset(asset, meta)*`.
33
- /// @return Block-stream response containing one `response(allowed)` per asset block.
35
+ /// @return Block-stream response containing one `status(ok)` per asset block.
34
36
  function isAllowedAsset(bytes calldata request) external view returns (bytes memory) {
35
37
  (Cur memory query, uint count, ) = cursor(request, 1);
36
- Writer memory response = Writers.alloc32s(count);
38
+ Writer memory response = Writers.allocStatuses(count);
37
39
 
38
40
  while (query.i < query.bound) {
39
41
  (bytes32 asset, bytes32 meta) = query.unpackAsset();
40
42
  bool allowed = isAllowedAsset(asset, meta);
41
- Writers.appendBool(response, Keys.Response, allowed);
43
+ response.appendStatus(allowed);
42
44
  }
43
45
 
44
46
  return query.complete(response);
@@ -1,25 +1,15 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import {Cur, Cursors, Schemas, Writer, Writers} from "../Cursors.sol";
4
+ import {Cur, Cursors, Forms, Writer, Writers} from "../Cursors.sol";
5
5
  import {QueryBase} from "./Base.sol";
6
6
 
7
7
  using Cursors for Cur;
8
+ using Writers for Writer;
8
9
 
9
10
  string constant NAME = "getBalances";
10
- string constant INPUT = "query(bytes32 account, bytes32 asset, bytes32 meta)";
11
-
12
- /// @title GetBalances
13
- /// @notice Rootzero query that resolves balances for one or more `(account, asset, meta)` tuples.
14
- /// The request is a run of `QUERY` blocks, each encoding `(bytes32 account, bytes32 asset, bytes32 meta)`.
15
- /// The response returns one `BALANCE` block per query entry, preserving request order.
16
- abstract contract GetBalances is QueryBase {
17
- uint public immutable getBalancesId = queryId(NAME);
18
-
19
- constructor() {
20
- emit Query(host, NAME, INPUT, Schemas.Balance, getBalancesId);
21
- }
22
11
 
12
+ abstract contract GetBalancesHook {
23
13
  /// @notice Resolve one account's balance for one supported asset.
24
14
  /// Concrete implementations define how assets are resolved.
25
15
  /// @param account Account identifier carried by the query payload.
@@ -27,18 +17,30 @@ abstract contract GetBalances is QueryBase {
27
17
  /// @param meta Requested asset metadata slot.
28
18
  /// @return amount Current balance in the asset's native units.
29
19
  function getBalance(bytes32 account, bytes32 asset, bytes32 meta) internal view virtual returns (uint amount);
20
+ }
21
+
22
+ /// @title GetBalances
23
+ /// @notice Rootzero query that resolves balances for one or more `(account, asset, meta)` tuples.
24
+ /// The request is a run of `ACCOUNT_ASSET` form blocks.
25
+ /// The response returns one `ACCOUNT_AMOUNT` form block per requested position, preserving request order.
26
+ abstract contract GetBalances is QueryBase, GetBalancesHook {
27
+ uint public immutable getBalancesId = queryId(NAME);
28
+
29
+ constructor() {
30
+ emit Query(host, getBalancesId, NAME, Forms.AccountAsset, Forms.AccountAmount);
31
+ }
30
32
 
31
33
  /// @notice Resolve balances for a run of requested `(account, asset, meta)` tuples.
32
- /// @param request Block-stream request consisting of `query(account, asset, meta)*`.
33
- /// @return Block-stream response containing one `balance(asset, meta, amount)` per query block.
34
+ /// @param request Block-stream request consisting of `accountAsset(account, asset, meta)*`.
35
+ /// @return Block-stream response containing one `accountAmount(account, asset, meta, amount)` block per request block.
34
36
  function getBalances(bytes calldata request) external view returns (bytes memory) {
35
37
  (Cur memory query, uint count, ) = cursor(request, 1);
36
- Writer memory response = Writers.allocBalances(count);
38
+ Writer memory response = Writers.allocAccountAmounts(count);
37
39
 
38
40
  while (query.i < query.bound) {
39
- (bytes32 account, bytes32 asset, bytes32 meta) = query.unpackQuery96();
40
- uint balance = getBalance(account, asset, meta);
41
- Writers.appendBalance(response, asset, meta, balance);
41
+ (bytes32 account, bytes32 asset, bytes32 meta) = query.unpackAccountAsset();
42
+ uint amount = getBalance(account, asset, meta);
43
+ response.appendAccountAmount(account, asset, meta, amount);
42
44
  }
43
45
 
44
46
  return query.complete(response);
package/queries/Base.sol CHANGED
@@ -1,8 +1,7 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import { CursorBase } from "../core/CursorBase.sol";
5
- import { HostBound } from "../core/HostBound.sol";
4
+ import { RootZeroContext } from "../core/Context.sol";
6
5
  import { QueryEvent } from "../events/Query.sol";
7
6
  import { Ids, Selectors } from "../utils/Ids.sol";
8
7
 
@@ -21,7 +20,7 @@ function encodeQueryCall(uint target, bytes calldata request) pure returns (byte
21
20
  /// @notice Abstract base for rootzero query contracts.
22
21
  /// Queries are view-only entry points that consume a block-stream request and
23
22
  /// return a block-stream response.
24
- abstract contract QueryBase is CursorBase, HostBound, QueryEvent {
23
+ abstract contract QueryBase is RootZeroContext, QueryEvent {
25
24
 
26
25
  /// @notice Derive the deterministic node ID for a named query on this contract.
27
26
  /// The ID encodes the ABI selector of `name(bytes)` and `address(this)`,
@@ -1,47 +1,55 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import {Cur, Cursors, Writer, Writers, Keys} from "../Cursors.sol";
5
- import {Schemas} from "../blocks/Schema.sol";
4
+ import {Cur, Cursors, Writer, Writers} from "../Cursors.sol";
5
+ import {Forms} from "../blocks/Schema.sol";
6
6
  import {QueryBase} from "./Base.sol";
7
7
 
8
8
  using Cursors for Cur;
9
9
 
10
- string constant NAME = "getAssetPosition";
10
+ string constant NAME = "getPosition";
11
11
 
12
- /// @title PositionsQuery
13
- /// @notice Rootzero query that resolves one dynamic position response for each requested asset tuple.
14
- /// The request is a run of `ASSET` blocks.
15
- /// The response returns one dynamic `RESPONSE` block per asset entry, preserving request order.
16
- abstract contract AssetPosition is QueryBase {
17
- uint public immutable getAssetPositionId = queryId(NAME);
18
- uint internal immutable positionResponseSize;
19
-
20
- constructor(string memory output, uint responseSize) {
21
- positionResponseSize = responseSize;
22
- emit Query(host, NAME, Schemas.Asset, output, getAssetPositionId);
23
- }
24
-
25
- /// @notice Resolve the position payload for one requested asset tuple.
12
+ abstract contract GetPositionHook {
13
+ /// @notice Resolve the position payload for one requested position.
26
14
  /// Concrete implementations must append exactly one `RESPONSE` block whose payload
27
15
  /// length matches `positionResponseSize`.
16
+ /// @param account Requested account identifier.
28
17
  /// @param asset Requested asset identifier.
29
18
  /// @param meta Requested asset metadata slot.
30
19
  /// @param response Destination writer for the response stream.
31
- function appendAssetPosition(bytes32 asset, bytes32 meta, Writer memory response) internal view virtual;
20
+ function appendPosition(
21
+ bytes32 account,
22
+ bytes32 asset,
23
+ bytes32 meta,
24
+ Writer memory response
25
+ ) internal view virtual;
26
+ }
27
+
28
+ /// @title GetPosition
29
+ /// @notice Rootzero query that resolves one dynamic position response for each requested position.
30
+ /// The request is a run of `ACCOUNT_ASSET` form blocks.
31
+ /// The response returns one dynamic `RESPONSE` block per position entry, preserving request order.
32
+ abstract contract GetPosition is QueryBase, GetPositionHook {
33
+ uint public immutable getPositionId = queryId(NAME);
34
+ uint internal immutable positionResponseSize;
35
+
36
+ constructor(string memory output, uint responseSize) {
37
+ positionResponseSize = responseSize;
38
+ emit Query(host, getPositionId, NAME, Forms.AccountAsset, output);
39
+ }
32
40
 
33
- /// @notice Resolve positions for a run of requested `(asset, meta)` tuples.
41
+ /// @notice Resolve positions for a run of requested `(account, asset, meta)` tuples.
34
42
  /// @dev Allocates from the configured fixed response payload length so each hook call
35
43
  /// can append one `RESPONSE` block directly into the output stream.
36
- /// @param request Block-stream request consisting of `asset(asset, meta)*`.
37
- /// @return Block-stream response containing one `response(bytes data)` block per asset block.
38
- function getAssetPosition(bytes calldata request) external view returns (bytes memory) {
44
+ /// @param request Block-stream request consisting of `accountAsset(account, asset, meta)*`.
45
+ /// @return Block-stream response containing one `response(bytes data)` block per position block.
46
+ function getPosition(bytes calldata request) external view returns (bytes memory) {
39
47
  (Cur memory query, uint count, ) = cursor(request, 1);
40
48
  Writer memory response = Writers.allocBytes(count, positionResponseSize);
41
49
 
42
50
  while (query.i < query.bound) {
43
- (bytes32 asset, bytes32 meta) = query.unpackAsset();
44
- appendAssetPosition(asset, meta, response);
51
+ (bytes32 account, bytes32 asset, bytes32 meta) = query.unpackAccountAsset();
52
+ appendPosition(account, asset, meta, response);
45
53
  }
46
54
 
47
55
  return query.complete(response);
@@ -10,7 +10,6 @@ import {isFamily, toLocalBase, toUnspecifiedBase} from "./Utils.sol";
10
10
  /// Account IDs embed a 4-byte type tag in bits [255:224]:
11
11
  /// - `Admin` — chain-local EVM address in bits [191:32]
12
12
  /// - `User` — chain-agnostic EVM address in bits [191:32]
13
- /// - `Keccak` — 28-byte keccak hash of an arbitrary key
14
13
  library Accounts {
15
14
  /// @dev Thrown when an account ID does not belong to the EVM family.
16
15
  error InvalidAccount();
@@ -31,12 +30,21 @@ library Accounts {
31
30
  return uint32(uint(account) >> 224);
32
31
  }
33
32
 
33
+ /// @notice Return true if `account` uses the Account category tag in the type field.
34
+ function isAccount(bytes32 account) internal pure returns (bool) {
35
+ return uint8(uint(account) >> 232) == Layout.Account;
36
+ }
37
+
34
38
  /// @notice Return true if `account` is an admin account.
35
39
  function isAdmin(bytes32 account) internal pure returns (bool) {
36
40
  return prefix(account) == Admin;
37
41
  }
38
42
 
39
- /// @notice Return true if `account` is a keccak account.
43
+ /// @notice Return true if `account` is a user account.
44
+ function isUser(bytes32 account) internal pure returns (bool) {
45
+ return prefix(account) == User;
46
+ }
47
+
40
48
  function isKeccak(bytes32 account) internal pure returns (bool) {
41
49
  return prefix(account) == Keccak;
42
50
  }
@@ -55,19 +63,12 @@ library Accounts {
55
63
  return bytes32(toUnspecifiedBase(User) | (uint(uint160(addr)) << 32));
56
64
  }
57
65
 
58
- /// @notice Encode arbitrary calldata as a keccak account ID.
59
- /// The lower 28 bytes of the ID hold the lower 28 bytes of `keccak256(raw)`.
60
- /// @param raw Arbitrary key bytes to hash.
61
- /// @return Keccak account ID.
62
- function toKeccak(bytes calldata raw) internal pure returns (bytes32) {
63
- return bytes32(toUnspecifiedBase(Keccak) | uint224(uint256(keccak256(raw))));
66
+ function toKeccak(bytes32 head, bytes32 meta) internal pure returns (bytes32) {
67
+ return bytes32(toUnspecifiedBase(Keccak) | uint224(uint256(keccak256(bytes.concat(head, meta)))));
64
68
  }
65
69
 
66
- /// @notice Return true if `account` is the keccak account ID of `raw`.
67
- /// @param account Account ID to compare.
68
- /// @param raw Raw key bytes to hash and compare against.
69
- function matchesKeccak(bytes32 account, bytes calldata raw) internal pure returns (bool) {
70
- return account == toKeccak(raw);
70
+ function matchesKeccak(bytes32 account, bytes32 head, bytes32 meta) internal pure returns (bool) {
71
+ return account == toKeccak(head, meta);
71
72
  }
72
73
 
73
74
  /// @notice Assert that `account` uses the Account layout tag and return it unchanged.
package/utils/Assets.sol CHANGED
@@ -10,7 +10,8 @@ import {matchesBase, toLocalBase} from "./Utils.sol";
10
10
  /// Asset IDs embed a 4-byte type tag in bits [255:224]:
11
11
  /// - `Value` — native chain value (ETH); no address payload
12
12
  /// - `Erc20` — ERC-20 token; contract address in bits [191:32]
13
- /// - `Erc721` — ERC-721 collection; issuer address in bits [191:32]
13
+ /// - `Erc721` — ERC-721 collection; collection address in bits [191:32]
14
+ /// - `Erc1155` — ERC-1155 collection; collection address in bits [191:32]
14
15
  ///
15
16
  /// All asset IDs are chain-local (include `block.chainid` in bits [223:192]).
16
17
  library Assets {
@@ -22,11 +23,75 @@ library Assets {
22
23
  /// @dev Full 4-byte type prefix for ERC-20 assets.
23
24
  uint32 constant Erc20 = (uint32(Layout.Evm32) << 16) | (uint32(Layout.Asset) << 8) | uint32(Layout.Erc20);
24
25
  /// @dev Full 4-byte type prefix for ERC-721 assets.
25
- uint32 constant Erc721 = (uint32(Layout.Evm32) << 16) | (uint32(Layout.Asset) << 8) | uint32(Layout.Erc721);
26
+ uint32 constant Erc721 = (uint32(Layout.Evm64) << 16) | (uint32(Layout.Asset) << 8) | uint32(Layout.Erc721);
27
+ /// @dev Full 4-byte type prefix for ERC-1155 assets.
28
+ uint32 constant Erc1155 = (uint32(Layout.Evm64) << 16) | (uint32(Layout.Asset) << 8) | uint32(Layout.Erc1155);
26
29
 
27
- /// @notice Return true if `asset` uses the 32-byte EVM layout (top byte is `0x20`).
30
+ /// @notice Return true if `asset` uses the Asset category tag in the type field.
31
+ function isAsset(bytes32 asset) internal pure returns (bool) {
32
+ return uint8(uint(asset) >> 232) == Layout.Asset;
33
+ }
34
+
35
+ /// @notice Return true if `asset` uses the 32-byte asset layout with no metadata identity (top byte is `0x20`).
28
36
  function is32(bytes32 asset) internal pure returns (bool) {
29
- return bytes1(asset) == 0x20;
37
+ return isAsset(asset) && bytes1(asset) == 0x20;
38
+ }
39
+
40
+ /// @notice Return true if `asset` uses the 64-byte asset layout with metadata-backed identity (top byte is `0x40`).
41
+ function is64(bytes32 asset) internal pure returns (bool) {
42
+ return isAsset(asset) && bytes1(asset) == 0x40;
43
+ }
44
+
45
+ /// @notice Return true if `asset` is the local native value asset.
46
+ function isValue(bytes32 asset) internal view returns (bool) {
47
+ return asset == toValue();
48
+ }
49
+
50
+ /// @notice Return true if `asset` is a local ERC-20 asset.
51
+ function isErc20(bytes32 asset) internal view returns (bool) {
52
+ return matchesBase(asset, toLocalBase(Erc20));
53
+ }
54
+
55
+ /// @notice Return true if `asset` is a local ERC-721 asset.
56
+ function isErc721(bytes32 asset) internal view returns (bool) {
57
+ return matchesBase(asset, toLocalBase(Erc721));
58
+ }
59
+
60
+ /// @notice Return true if `asset` is a local ERC-1155 asset.
61
+ function isErc1155(bytes32 asset) internal view returns (bool) {
62
+ return matchesBase(asset, toLocalBase(Erc1155));
63
+ }
64
+
65
+ /// @notice Assert that `input` is the local native value asset and return it unchanged.
66
+ /// @param input Asset identifier to validate.
67
+ /// @return asset The same `input` if it is the local native value asset.
68
+ function value(bytes32 input) internal view returns (bytes32 asset) {
69
+ if (!isValue(input)) revert InvalidAsset();
70
+ return input;
71
+ }
72
+
73
+ /// @notice Assert that `input` is a local ERC-20 asset and return it unchanged.
74
+ /// @param input Asset identifier to validate.
75
+ /// @return asset The same `input` if it is a local ERC-20 asset.
76
+ function erc20(bytes32 input) internal view returns (bytes32 asset) {
77
+ if (!isErc20(input)) revert InvalidAsset();
78
+ return input;
79
+ }
80
+
81
+ /// @notice Assert that `input` is a local ERC-721 asset and return it unchanged.
82
+ /// @param input Asset identifier to validate.
83
+ /// @return asset The same `input` if it is a local ERC-721 asset.
84
+ function erc721(bytes32 input) internal view returns (bytes32 asset) {
85
+ if (!isErc721(input)) revert InvalidAsset();
86
+ return input;
87
+ }
88
+
89
+ /// @notice Assert that `input` is a local ERC-1155 asset and return it unchanged.
90
+ /// @param input Asset identifier to validate.
91
+ /// @return asset The same `input` if it is a local ERC-1155 asset.
92
+ function erc1155(bytes32 input) internal view returns (bytes32 asset) {
93
+ if (!isErc1155(input)) revert InvalidAsset();
94
+ return input;
30
95
  }
31
96
 
32
97
  /// @notice Create a chain-local native value asset ID.
@@ -42,49 +107,18 @@ library Assets {
42
107
  return bytes32(toLocalBase(Erc20) | (uint(uint160(addr)) << 32));
43
108
  }
44
109
 
45
- /// @notice Create a chain-local ERC-721 asset ID for `issuer`.
46
- /// @param issuer ERC-721 collection contract address.
47
- /// @return Asset ID with `issuer` embedded in bits [191:32].
48
- function toErc721(address issuer) internal view returns (bytes32) {
49
- return bytes32(toLocalBase(Erc721) | (uint(uint160(issuer)) << 32));
110
+ /// @notice Create a chain-local ERC-721 asset ID for `collection`.
111
+ /// @param collection ERC-721 collection contract address.
112
+ /// @return Asset ID with `collection` embedded in bits [191:32].
113
+ function toErc721(address collection) internal view returns (bytes32) {
114
+ return bytes32(toLocalBase(Erc721) | (uint(uint160(collection)) << 32));
50
115
  }
51
116
 
52
- /// @notice Derive a storage key for an (asset, meta) pair.
53
- /// For 32-byte EVM assets (no meta), the key is the asset ID itself.
54
- /// For assets with metadata (e.g. ERC-721 token IDs), the key is
55
- /// `keccak256(asset ++ meta)`.
56
- /// Reverts if `asset` is zero, or if it is a 32-byte asset but `meta` is non-zero.
57
- /// @param asset Asset identifier.
58
- /// @param meta Asset metadata slot (e.g. token ID context).
59
- /// @return Storage key for the (asset, meta) combination.
60
- function key(bytes32 asset, bytes32 meta) internal pure returns (bytes32) {
61
- if (asset == 0 || (bytes1(asset) == 0x20 && meta != 0)) revert InvalidAsset();
62
- return bytes1(asset) == 0x20 ? asset : keccak256(bytes.concat(asset, meta));
63
- }
64
-
65
- /// @notice Return true when two local ERC-20 assets are already in canonical token-address order.
66
- /// Useful for pair-style integrations that require a stable token ordering
67
- /// regardless of the caller's input order.
68
- /// Reverts if either asset is not a local ERC-20 asset.
69
- /// @param a First ERC-20 asset identifier.
70
- /// @param b Second ERC-20 asset identifier.
71
- /// @return ordered Whether `a`'s token address is lower than `b`'s token address.
72
- function isSortedErc20(bytes32 a, bytes32 b) internal view returns (bool ordered) {
73
- return erc20Addr(a) < erc20Addr(b);
74
- }
75
-
76
- /// @notice Extract the token addresses for two local ERC-20 assets and report whether they are already ordered.
77
- /// The returned addresses preserve the original input order.
78
- /// Reverts if either asset is not a local ERC-20 asset.
79
- /// @param a First ERC-20 asset identifier.
80
- /// @param b Second ERC-20 asset identifier.
81
- /// @return addrA Token address extracted from `a`.
82
- /// @return addrB Token address extracted from `b`.
83
- /// @return ordered Whether `addrA` is lower than `addrB`.
84
- function erc20Addrs(bytes32 a, bytes32 b) internal view returns (address addrA, address addrB, bool ordered) {
85
- addrA = erc20Addr(a);
86
- addrB = erc20Addr(b);
87
- ordered = addrA < addrB;
117
+ /// @notice Create a chain-local ERC-1155 asset ID for `collection`.
118
+ /// @param collection ERC-1155 collection contract address.
119
+ /// @return Asset ID with `collection` embedded in bits [191:32].
120
+ function toErc1155(address collection) internal view returns (bytes32) {
121
+ return bytes32(toLocalBase(Erc1155) | (uint(uint160(collection)) << 32));
88
122
  }
89
123
 
90
124
  /// @notice Extract the ERC-20 contract address from an asset ID.
@@ -92,17 +126,68 @@ library Assets {
92
126
  /// @param asset ERC-20 asset identifier.
93
127
  /// @return Token contract address embedded in bits [191:32].
94
128
  function erc20Addr(bytes32 asset) internal view returns (address) {
95
- if (!matchesBase(asset, toLocalBase(Erc20))) revert InvalidAsset();
96
- return address(uint160(uint(asset) >> 32));
129
+ return address(uint160(uint(erc20(asset)) >> 32));
130
+ }
131
+
132
+ /// @notice Assert that `asset` is a local ERC-20 for `token` and return it unchanged.
133
+ /// Reverts if `asset` is not a local ERC-20 asset or if its token address differs.
134
+ /// @param asset ERC-20 asset identifier.
135
+ /// @param token Expected token contract address.
136
+ /// @return The same `asset` value if valid.
137
+ function matchErc20(bytes32 asset, address token) internal view returns (bytes32) {
138
+ if (erc20Addr(asset) != token) revert InvalidAsset();
139
+ return asset;
97
140
  }
98
141
 
99
- /// @notice Extract the ERC-721 issuer address from an asset ID.
142
+ /// @notice Extract the ERC-721 collection address from an asset ID.
100
143
  /// Reverts if `asset` is not a local ERC-721 asset.
101
144
  /// @param asset ERC-721 asset identifier.
102
- /// @return Issuer contract address embedded in bits [191:32].
103
- function erc721Issuer(bytes32 asset) internal view returns (address) {
104
- if (!matchesBase(asset, toLocalBase(Erc721))) revert InvalidAsset();
105
- return address(uint160(uint(asset) >> 32));
145
+ /// @return Collection contract address embedded in bits [191:32].
146
+ function erc721Collection(bytes32 asset) internal view returns (address) {
147
+ return address(uint160(uint(erc721(asset)) >> 32));
148
+ }
149
+
150
+ /// @notice Assert that `asset` is a local ERC-721 for `collection` and return it unchanged.
151
+ /// Reverts if `asset` is not a local ERC-721 asset or if its collection address differs.
152
+ /// @param asset ERC-721 asset identifier.
153
+ /// @param collection Expected ERC-721 collection address.
154
+ /// @return The same `asset` value if valid.
155
+ function matchErc721(bytes32 asset, address collection) internal view returns (bytes32) {
156
+ if (erc721Collection(asset) != collection) revert InvalidAsset();
157
+ return asset;
158
+ }
159
+
160
+ /// @notice Extract the ERC-1155 collection address from an asset ID.
161
+ /// Reverts if `asset` is not a local ERC-1155 asset.
162
+ /// @param asset ERC-1155 asset identifier.
163
+ /// @return Collection contract address embedded in bits [191:32].
164
+ function erc1155Collection(bytes32 asset) internal view returns (address) {
165
+ return address(uint160(uint(erc1155(asset)) >> 32));
166
+ }
167
+
168
+ /// @notice Assert that `asset` is a local ERC-1155 for `collection` and return it unchanged.
169
+ /// Reverts if `asset` is not a local ERC-1155 asset or if its collection address differs.
170
+ /// @param asset ERC-1155 asset identifier.
171
+ /// @param collection Expected ERC-1155 collection address.
172
+ /// @return The same `asset` value if valid.
173
+ function matchErc1155(bytes32 asset, address collection) internal view returns (bytes32) {
174
+ if (erc1155Collection(asset) != collection) revert InvalidAsset();
175
+ return asset;
176
+ }
177
+
178
+ /// @notice Derive a storage slot for an (asset, meta) pair.
179
+ /// For 32-byte EVM assets (no meta), the slot is the asset ID itself.
180
+ /// For assets with metadata (e.g. ERC-721 or ERC-1155 token IDs), the slot is
181
+ /// `keccak256(asset ++ meta)`.
182
+ /// Reverts only if `asset` is zero.
183
+ /// For 32-byte assets, `meta` is ignored and does not affect the derived slot.
184
+ /// @param asset Asset identifier.
185
+ /// @param meta Asset metadata slot (e.g. token ID context).
186
+ /// @return Storage slot for the (asset, meta) combination.
187
+ function slot(bytes32 asset, bytes32 meta) internal pure returns (bytes32) {
188
+ if (is32(asset)) return asset;
189
+ if (meta == 0 || !is64(asset)) revert InvalidAsset();
190
+ return keccak256(bytes.concat(asset, meta));
106
191
  }
107
192
  }
108
193
 
@@ -136,16 +221,6 @@ library Amounts {
136
221
  return amount;
137
222
  }
138
223
 
139
- /// @notice Assert non-zero amount and derive the storage key for the (asset, meta) pair.
140
- /// @param asset Asset identifier.
141
- /// @param meta Asset metadata slot.
142
- /// @param amount Amount to validate (must be non-zero).
143
- /// @return key_ Storage key from `Assets.key(asset, meta)`.
144
- function ensureKey(bytes32 asset, bytes32 meta, uint amount) internal pure returns (bytes32 key_) {
145
- ensure(amount);
146
- return Assets.key(asset, meta);
147
- }
148
-
149
224
  /// @notice Clamp `available` to `[min, max]`.
150
225
  /// Uses all of `available` if it does not exceed `max`; reverts if the result
151
226
  /// would fall below `min`.
package/utils/Ids.sol CHANGED
@@ -81,8 +81,8 @@ library Ids {
81
81
 
82
82
  /// @notice Assert that `id` is a query ID and return it unchanged.
83
83
  /// @param id Node ID to validate.
84
- /// @return qid The same `id` value if it is a query.
85
- function query(uint id) internal pure returns (uint qid) {
84
+ /// @return queryId The same `id` value if it is a query.
85
+ function query(uint id) internal pure returns (uint queryId) {
86
86
  if (!isQuery(id)) revert InvalidId();
87
87
  return id;
88
88
  }
@@ -95,12 +95,12 @@ library Ids {
95
95
  return bytes4(uint32(id >> 160));
96
96
  }
97
97
 
98
- /// @notice Assert that `id` is the host ID of `target` on the current chain.
98
+ /// @notice Assert that `id` is the host ID of `addr` on the current chain.
99
99
  /// @param id Node ID to validate.
100
- /// @param target Expected host contract address.
101
- /// @return hid The same `id` value if it matches `target`.
102
- function host(uint id, address target) internal view returns (uint hid) {
103
- if (id != toHost(target)) revert InvalidId();
100
+ /// @param addr Expected host contract address.
101
+ /// @return hid The same `id` value if it matches `addr`.
102
+ function matchHost(uint id, address addr) internal view returns (uint hid) {
103
+ if (id != toHost(addr)) revert InvalidId();
104
104
  return id;
105
105
  }
106
106
 
@@ -163,8 +163,8 @@ library Ids {
163
163
  /// @title Selectors
164
164
  /// @notice ABI-selector derivation helpers for command, peer, and query dispatch.
165
165
  library Selectors {
166
- /// @dev ABI argument encoding for command entry points: `((uint256,bytes32,bytes,bytes))`.
167
- string constant CommandArgs = "((uint256,bytes32,bytes,bytes))";
166
+ /// @dev ABI argument encoding for command entry points: `((bytes32,bytes,bytes))`.
167
+ string constant CommandArgs = "((bytes32,bytes,bytes))";
168
168
  /// @dev ABI argument encoding for peer entry points: `(bytes)`.
169
169
  string constant PeerArgs = "(bytes)";
170
170
  /// @dev ABI argument encoding for query entry points: `(bytes)`.
package/utils/Layout.sol CHANGED
@@ -17,7 +17,7 @@ library Layout {
17
17
  uint16 constant Opaque32 = 0x2000;
18
18
  /// @dev 32-byte EVM-compatible value; lower 20 bytes hold an address.
19
19
  uint16 constant Evm32 = 0x2001;
20
- /// @dev 64-byte EVM-compatible value (reserved for extended IDs).
20
+ /// @dev 64-byte EVM-compatible value; used when a paired metadata word completes the identity.
21
21
  uint16 constant Evm64 = 0x4001;
22
22
 
23
23
  // -------------------------------------------------------------------------
@@ -39,7 +39,7 @@ library Layout {
39
39
  uint8 constant Admin = 0x01;
40
40
  /// @dev User account — chain-agnostic, backed by an EVM address.
41
41
  uint8 constant User = 0x02;
42
- /// @dev Keccak account — opaque 28-byte hash of an arbitrary key.
42
+ /// @dev Keccak account — opaque 28-byte keccak commitment.
43
43
  uint8 constant Keccak = 0x03;
44
44
 
45
45
  // -------------------------------------------------------------------------
@@ -63,6 +63,8 @@ library Layout {
63
63
  uint8 constant Value = 0x01;
64
64
  /// @dev ERC-20 fungible token; lower 20 bytes of the ID hold the contract address.
65
65
  uint8 constant Erc20 = 0x02;
66
- /// @dev ERC-721 non-fungible token; lower 20 bytes of the ID hold the issuer address.
66
+ /// @dev ERC-721 non-fungible token; lower 20 bytes of the ID hold the collection address.
67
67
  uint8 constant Erc721 = 0x03;
68
+ /// @dev ERC-1155 multi-token asset; lower 20 bytes of the ID hold the collection address.
69
+ uint8 constant Erc1155 = 0x04;
68
70
  }
package/utils/Utils.sol CHANGED
@@ -92,6 +92,16 @@ function addrOr(address addr, address or) pure returns (address) {
92
92
  return addr == address(0) ? or : addr;
93
93
  }
94
94
 
95
+ /// @notice Convert a signed integer to its 32-byte two's-complement representation.
96
+ function intToBytes32(int value) pure returns (bytes32) {
97
+ return bytes32(uint(value));
98
+ }
99
+
100
+ /// @notice Convert a 32-byte two's-complement representation to a signed integer.
101
+ function bytes32ToInt(bytes32 value) pure returns (int) {
102
+ return int(uint(value));
103
+ }
104
+
95
105
  /// @notice Convert a null-terminated `bytes32` value to a Solidity string.
96
106
  /// Stops at the first zero byte and returns only the meaningful prefix.
97
107
  function bytes32ToString(bytes32 value) pure returns (string memory result) {