@rootzero/contracts 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/Endpoints.sol +4 -3
- package/Events.sol +1 -1
- package/README.md +58 -31
- package/Utils.sol +4 -3
- package/blocks/Cursors.sol +83 -143
- package/blocks/Keys.sol +15 -15
- package/blocks/Schema.sol +27 -28
- package/blocks/Writers.sol +26 -33
- package/commands/Base.sol +2 -2
- package/commands/Burn.sol +3 -4
- package/commands/Credit.sol +3 -4
- package/commands/Debit.sol +4 -5
- package/commands/Deposit.sol +8 -10
- package/commands/Payout.sol +3 -6
- package/commands/Withdraw.sol +3 -4
- package/commands/admin/AllowAssets.sol +5 -6
- package/commands/admin/Allowance.sol +3 -4
- package/commands/admin/DenyAssets.sol +5 -6
- package/commands/admin/Execute.sol +2 -2
- package/core/Access.sol +2 -2
- package/core/Balances.sol +10 -11
- package/core/Calls.sol +7 -7
- package/core/Host.sol +2 -2
- package/core/Runtime.sol +3 -3
- package/core/Types.sol +0 -14
- package/docs/Schema.md +29 -10
- package/events/Asset.sol +17 -3
- package/events/Balance.sol +2 -3
- package/events/Locked.sol +2 -3
- package/events/Position.sol +2 -3
- package/events/Received.sol +2 -3
- package/events/Spent.sol +2 -3
- package/events/Unlocked.sol +2 -3
- package/guards/Base.sol +4 -4
- package/package.json +1 -1
- package/peer/AllowAssets.sol +3 -3
- package/peer/Allowance.sol +2 -2
- package/peer/Base.sol +4 -4
- package/peer/Credit.sol +10 -10
- package/peer/Debit.sol +10 -10
- package/peer/DenyAssets.sol +3 -3
- package/peer/Recover.sol +51 -0
- package/peer/Redeem.sol +48 -0
- package/peer/Settle.sol +3 -3
- package/queries/Assets.sol +7 -8
- package/queries/Balances.sol +8 -9
- package/queries/Base.sol +4 -4
- package/queries/Positions.sol +4 -6
- package/utils/Accounts.sol +76 -58
- package/utils/Assets.sol +55 -115
- package/utils/Ids.sol +33 -233
- package/utils/Layout.sol +11 -17
- package/utils/Nodes.sol +263 -0
- package/utils/Utils.sol +9 -24
- package/peer/BalancePull.sol +0 -49
package/peer/Redeem.sol
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
|
+
pragma solidity ^0.8.33;
|
|
3
|
+
|
|
4
|
+
import {PeerBase} from "./Base.sol";
|
|
5
|
+
import {Cursors, Cur, Schemas} from "../Cursors.sol";
|
|
6
|
+
|
|
7
|
+
using Cursors for Cur;
|
|
8
|
+
|
|
9
|
+
interface IPeerRedeemBalance {
|
|
10
|
+
function peerRedeemBalance(bytes calldata request) external returns (bytes memory);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
abstract contract RedeemBalanceHook {
|
|
14
|
+
/// @notice Override to redeem one balance claim from a peer host into local assets.
|
|
15
|
+
/// @param peer Peer host node ID for this request.
|
|
16
|
+
/// @param asset Asset identifier to redeem locally.
|
|
17
|
+
/// @param amount Amount to redeem in the asset's native units.
|
|
18
|
+
function redeemBalance(uint peer, bytes32 asset, uint amount) internal virtual;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/// @title PeerRedeemBalance
|
|
22
|
+
/// @notice Peer that redeems balance state from a peer host into local assets.
|
|
23
|
+
/// Each BALANCE block in the request calls `redeemBalance(peer, asset, amount)`.
|
|
24
|
+
/// Restricted to trusted peers.
|
|
25
|
+
abstract contract PeerRedeemBalance is PeerBase, RedeemBalanceHook, IPeerRedeemBalance {
|
|
26
|
+
uint internal immutable peerRedeemBalanceId = peerId(this.peerRedeemBalance.selector);
|
|
27
|
+
|
|
28
|
+
constructor() {
|
|
29
|
+
emit Peer(host, peerRedeemBalanceId, "1:0", Schemas.Balance, "", false);
|
|
30
|
+
emit Labeled(peerRedeemBalanceId, bytes32(0), "peerRedeemBalance");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// @notice Execute the balance redemption peer call.
|
|
34
|
+
/// @param request BALANCE block stream supplied by the trusted peer.
|
|
35
|
+
/// @return Empty response bytes.
|
|
36
|
+
function peerRedeemBalance(bytes calldata request) external onlyPeer returns (bytes memory) {
|
|
37
|
+
(Cur memory input, , ) = Cursors.init(request, 1);
|
|
38
|
+
uint peer = caller();
|
|
39
|
+
|
|
40
|
+
while (input.i < input.len) {
|
|
41
|
+
(bytes32 asset, uint amount) = input.unpackBalance();
|
|
42
|
+
redeemBalance(peer, asset, amount);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
input.complete();
|
|
46
|
+
return "";
|
|
47
|
+
}
|
|
48
|
+
}
|
package/peer/Settle.sol
CHANGED
|
@@ -30,9 +30,9 @@ abstract contract PeerSettle is PeerBase, DebitAccountHook, CreditAccountHook, I
|
|
|
30
30
|
(Cur memory state, , ) = Cursors.init(request, 1);
|
|
31
31
|
|
|
32
32
|
while (state.i < state.len) {
|
|
33
|
-
(bytes32 from, bytes32 to, bytes32 asset,
|
|
34
|
-
if (from != 0) debitAccount(from, asset,
|
|
35
|
-
if (to != 0) creditAccount(to, asset,
|
|
33
|
+
(bytes32 from, bytes32 to, bytes32 asset, uint amount) = state.unpackTransaction();
|
|
34
|
+
if (from != 0) debitAccount(from, asset, amount);
|
|
35
|
+
if (to != 0) creditAccount(to, asset, amount);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
state.complete();
|
package/queries/Assets.sol
CHANGED
|
@@ -9,16 +9,15 @@ using Cursors for Cur;
|
|
|
9
9
|
using Writers for Writer;
|
|
10
10
|
|
|
11
11
|
abstract contract AssetStatusHook {
|
|
12
|
-
/// @notice Resolve support status for one asset
|
|
12
|
+
/// @notice Resolve support status for one asset.
|
|
13
13
|
/// Concrete implementations define the support policy and optional context codes.
|
|
14
14
|
/// @param asset Requested asset identifier.
|
|
15
|
-
/// @param meta Requested asset metadata slot.
|
|
16
15
|
/// @return status Asset support status. Zero means unsupported; nonzero means supported.
|
|
17
|
-
function assetStatus(bytes32 asset
|
|
16
|
+
function assetStatus(bytes32 asset) internal view virtual returns (uint status);
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
/// @title AssetStatus
|
|
21
|
-
/// @notice Rootzero query that checks support status for one or more
|
|
20
|
+
/// @notice Rootzero query that checks support status for one or more assets.
|
|
22
21
|
/// The request is a run of `ASSET` blocks.
|
|
23
22
|
/// The response returns one `STATUS` form block per query entry, preserving request order.
|
|
24
23
|
abstract contract AssetStatus is QueryBase, AssetStatusHook {
|
|
@@ -29,16 +28,16 @@ abstract contract AssetStatus is QueryBase, AssetStatusHook {
|
|
|
29
28
|
emit Labeled(assetStatusId, bytes32(0), "assetStatus");
|
|
30
29
|
}
|
|
31
30
|
|
|
32
|
-
/// @notice Resolve asset support status for a run of requested
|
|
33
|
-
/// @param request Block-stream request consisting of `#asset { bytes32 asset
|
|
31
|
+
/// @notice Resolve asset support status for a run of requested assets.
|
|
32
|
+
/// @param request Block-stream request consisting of `#asset { bytes32 asset }` blocks.
|
|
34
33
|
/// @return Block-stream response containing one `#status { uint code }` per asset block.
|
|
35
34
|
function assetStatus(bytes calldata request) external view returns (bytes memory) {
|
|
36
35
|
(Cur memory query, uint groups, ) = Cursors.init(request, 1);
|
|
37
36
|
Writer memory response = Writers.allocStatuses(groups);
|
|
38
37
|
|
|
39
38
|
while (query.i < query.len) {
|
|
40
|
-
|
|
41
|
-
uint status = assetStatus(asset
|
|
39
|
+
bytes32 asset = query.unpackAsset();
|
|
40
|
+
uint status = assetStatus(asset);
|
|
42
41
|
response.appendStatus(status);
|
|
43
42
|
}
|
|
44
43
|
|
package/queries/Balances.sol
CHANGED
|
@@ -12,13 +12,12 @@ abstract contract GetBalancesHook {
|
|
|
12
12
|
/// Concrete implementations define how assets are resolved.
|
|
13
13
|
/// @param account Account identifier carried by the query payload.
|
|
14
14
|
/// @param asset Requested asset identifier.
|
|
15
|
-
/// @param meta Requested asset metadata slot.
|
|
16
15
|
/// @return amount Current balance in the asset's native units.
|
|
17
|
-
function getBalance(bytes32 account, bytes32 asset
|
|
16
|
+
function getBalance(bytes32 account, bytes32 asset) internal view virtual returns (uint amount);
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
/// @title GetBalances
|
|
21
|
-
/// @notice Rootzero query that resolves balances for one or more `(account, asset
|
|
20
|
+
/// @notice Rootzero query that resolves balances for one or more `(account, asset)` tuples.
|
|
22
21
|
/// The request is a run of `ACCOUNT_ASSET` form blocks.
|
|
23
22
|
/// The response returns one `ACCOUNT_AMOUNT` form block per requested position, preserving request order.
|
|
24
23
|
abstract contract GetBalances is QueryBase, GetBalancesHook {
|
|
@@ -29,17 +28,17 @@ abstract contract GetBalances is QueryBase, GetBalancesHook {
|
|
|
29
28
|
emit Labeled(getBalancesId, bytes32(0), "getBalances");
|
|
30
29
|
}
|
|
31
30
|
|
|
32
|
-
/// @notice Resolve balances for a run of requested `(account, asset
|
|
33
|
-
/// @param request Block-stream request consisting of `accountAsset(account, asset
|
|
34
|
-
/// @return Block-stream response containing one `accountAmount(account, asset,
|
|
31
|
+
/// @notice Resolve balances for a run of requested `(account, asset)` tuples.
|
|
32
|
+
/// @param request Block-stream request consisting of `accountAsset(account, asset)*`.
|
|
33
|
+
/// @return Block-stream response containing one `accountAmount(account, asset, amount)` block per request block.
|
|
35
34
|
function getBalances(bytes calldata request) external view returns (bytes memory) {
|
|
36
35
|
(Cur memory query, uint groups, ) = Cursors.init(request, 1);
|
|
37
36
|
Writer memory response = Writers.allocAccountAmounts(groups);
|
|
38
37
|
|
|
39
38
|
while (query.i < query.len) {
|
|
40
|
-
(bytes32 account, bytes32 asset
|
|
41
|
-
uint amount = getBalance(account, asset
|
|
42
|
-
response.appendAccountAmount(account, asset,
|
|
39
|
+
(bytes32 account, bytes32 asset) = query.unpackAccountAsset();
|
|
40
|
+
uint amount = getBalance(account, asset);
|
|
41
|
+
response.appendAccountAmount(account, asset, amount);
|
|
43
42
|
}
|
|
44
43
|
|
|
45
44
|
query.complete();
|
package/queries/Base.sol
CHANGED
|
@@ -4,16 +4,16 @@ pragma solidity ^0.8.33;
|
|
|
4
4
|
import { Runtime } from "../core/Runtime.sol";
|
|
5
5
|
import { LabeledEvent } from "../events/Labeled.sol";
|
|
6
6
|
import { QueryEvent } from "../events/Query.sol";
|
|
7
|
-
import {
|
|
7
|
+
import { Nodes } from "../utils/Nodes.sol";
|
|
8
8
|
|
|
9
9
|
/// @notice ABI-encode a query call from a target query ID and request block stream.
|
|
10
|
-
/// @dev Derives the function selector from `target` via `
|
|
10
|
+
/// @dev Derives the function selector from `target` via `Nodes.querySelector(target)`.
|
|
11
11
|
/// Reverts if `target` is not a valid query ID.
|
|
12
12
|
/// @param target Destination query node ID embedding the target selector.
|
|
13
13
|
/// @param request Input block stream for the query invocation.
|
|
14
14
|
/// @return ABI-encoded calldata for the query entry point.
|
|
15
15
|
function encodeQueryCall(uint target, bytes calldata request) pure returns (bytes memory) {
|
|
16
|
-
bytes4 selector =
|
|
16
|
+
bytes4 selector = Nodes.querySelector(target);
|
|
17
17
|
return abi.encodeWithSelector(selector, request);
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -29,6 +29,6 @@ abstract contract QueryBase is Runtime, QueryEvent, LabeledEvent {
|
|
|
29
29
|
/// @param selector Query entrypoint selector.
|
|
30
30
|
/// @return Query node ID.
|
|
31
31
|
function queryId(bytes4 selector) internal view returns (uint) {
|
|
32
|
-
return
|
|
32
|
+
return Nodes.toQuery(selector, address(this));
|
|
33
33
|
}
|
|
34
34
|
}
|
package/queries/Positions.sol
CHANGED
|
@@ -14,12 +14,10 @@ abstract contract GetPositionHook {
|
|
|
14
14
|
/// the query output schema.
|
|
15
15
|
/// @param account Requested account identifier.
|
|
16
16
|
/// @param asset Requested asset identifier.
|
|
17
|
-
/// @param meta Requested asset metadata slot.
|
|
18
17
|
/// @param response Destination writer for the response stream.
|
|
19
18
|
function appendPosition(
|
|
20
19
|
bytes32 account,
|
|
21
20
|
bytes32 asset,
|
|
22
|
-
bytes32 meta,
|
|
23
21
|
Writer memory response
|
|
24
22
|
) internal view virtual;
|
|
25
23
|
}
|
|
@@ -36,17 +34,17 @@ abstract contract GetPosition is QueryBase, GetPositionHook {
|
|
|
36
34
|
emit Labeled(getPositionId, bytes32(0), "getPosition");
|
|
37
35
|
}
|
|
38
36
|
|
|
39
|
-
/// @notice Resolve positions for a run of requested `(account, asset
|
|
37
|
+
/// @notice Resolve positions for a run of requested `(account, asset)` tuples.
|
|
40
38
|
/// @dev Allocates from a per-block capacity hint and grows when position outputs exceed it.
|
|
41
|
-
/// @param request Block-stream request consisting of `accountAsset(account, asset
|
|
39
|
+
/// @param request Block-stream request consisting of `accountAsset(account, asset)*`.
|
|
42
40
|
/// @return Block-stream response containing one output-schema block per position block.
|
|
43
41
|
function getPosition(bytes calldata request) external view returns (bytes memory) {
|
|
44
42
|
(Cur memory query, uint groups, ) = Cursors.init(request, 1);
|
|
45
43
|
Writer memory response = Writers.allocAny(groups);
|
|
46
44
|
|
|
47
45
|
while (query.i < query.len) {
|
|
48
|
-
(bytes32 account, bytes32 asset
|
|
49
|
-
appendPosition(account, asset,
|
|
46
|
+
(bytes32 account, bytes32 asset) = query.unpackAccountAsset();
|
|
47
|
+
appendPosition(account, asset, response);
|
|
50
48
|
}
|
|
51
49
|
|
|
52
50
|
query.complete();
|
package/utils/Accounts.sol
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
pragma solidity ^0.8.33;
|
|
3
3
|
|
|
4
4
|
import {Layout} from "./Layout.sol";
|
|
5
|
-
import {
|
|
5
|
+
import {Ids} from "./Ids.sol";
|
|
6
|
+
import {ensureAddr, isFamily, toLocalBase, toUnspecifiedBase} from "./Utils.sol";
|
|
6
7
|
|
|
7
8
|
/// @title Accounts
|
|
8
9
|
/// @notice Encoding and decoding helpers for 256-bit account identifiers.
|
|
@@ -11,18 +12,24 @@ import {isFamily, toLocalBase, toUnspecifiedBase} from "./Utils.sol";
|
|
|
11
12
|
/// - `Admin` — chain-local EVM address in bits [191:32]
|
|
12
13
|
/// - `Guardian` — chain-local EVM address in bits [191:32]
|
|
13
14
|
/// - `User` — chain-agnostic EVM address in bits [191:32]
|
|
15
|
+
///
|
|
16
|
+
/// If the first byte is zero, the account is an opaque
|
|
17
|
+
/// `0x00 || bytes31(hash)` ID. The full account identity must be supplied by
|
|
18
|
+
/// lookup or witness data when native account metadata is needed.
|
|
19
|
+
///
|
|
20
|
+
/// The helpers in this library validate and deconstruct structured account IDs.
|
|
14
21
|
library Accounts {
|
|
15
22
|
/// @dev Thrown when an account ID does not belong to the EVM family.
|
|
16
23
|
error InvalidAccount();
|
|
17
24
|
|
|
18
25
|
/// @dev 24-bit family tag shared by all EVM-backed account types.
|
|
19
|
-
uint24 constant Family = (uint24(Layout.
|
|
26
|
+
uint24 constant Family = (uint24(Layout.Evm) << 8) | uint24(Layout.Account);
|
|
20
27
|
/// @dev Full 4-byte type prefix for admin accounts (chain-local EVM address).
|
|
21
|
-
uint32 constant Admin = (uint32(Layout.
|
|
28
|
+
uint32 constant Admin = (uint32(Layout.Evm) << 16) | (uint32(Layout.Account) << 8) | uint32(Layout.Admin);
|
|
22
29
|
/// @dev Full 4-byte type prefix for guardian accounts (chain-local EVM address).
|
|
23
|
-
uint32 constant Guardian = (uint32(Layout.
|
|
30
|
+
uint32 constant Guardian = (uint32(Layout.Evm) << 16) | (uint32(Layout.Account) << 8) | uint32(Layout.Guardian);
|
|
24
31
|
/// @dev Full 4-byte type prefix for user accounts (chain-agnostic EVM address).
|
|
25
|
-
uint32 constant User = (uint32(Layout.
|
|
32
|
+
uint32 constant User = (uint32(Layout.Evm) << 16) | (uint32(Layout.Account) << 8) | uint32(Layout.User);
|
|
26
33
|
|
|
27
34
|
/// @notice Extract the 4-byte type prefix from an account ID.
|
|
28
35
|
/// @param account Account identifier.
|
|
@@ -31,9 +38,14 @@ library Accounts {
|
|
|
31
38
|
return uint32(uint(account) >> 224);
|
|
32
39
|
}
|
|
33
40
|
|
|
34
|
-
/// @notice Return true if `account`
|
|
35
|
-
function
|
|
36
|
-
return
|
|
41
|
+
/// @notice Return true if `account` belongs to the EVM account family.
|
|
42
|
+
function isEvm(bytes32 account) internal pure returns (bool) {
|
|
43
|
+
return isFamily(uint(account), Family);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// @notice Return true if `account` is opaque.
|
|
47
|
+
function isOpaque(bytes32 account) internal pure returns (bool) {
|
|
48
|
+
return Ids.isOpaque(account);
|
|
37
49
|
}
|
|
38
50
|
|
|
39
51
|
/// @notice Return true if `account` is an admin account.
|
|
@@ -51,82 +63,88 @@ library Accounts {
|
|
|
51
63
|
return prefix(account) == User;
|
|
52
64
|
}
|
|
53
65
|
|
|
54
|
-
/// @notice Assert that `
|
|
55
|
-
/// @param
|
|
56
|
-
/// @return account The same `
|
|
57
|
-
function
|
|
58
|
-
if (!
|
|
59
|
-
return
|
|
66
|
+
/// @notice Assert that `value` belongs to the EVM account family and return it unchanged.
|
|
67
|
+
/// @param value Account identifier to validate.
|
|
68
|
+
/// @return account The same `value` if it is an EVM account.
|
|
69
|
+
function evm(bytes32 value) internal pure returns (bytes32 account) {
|
|
70
|
+
if (!isEvm(value)) revert InvalidAccount();
|
|
71
|
+
return value;
|
|
60
72
|
}
|
|
61
73
|
|
|
62
|
-
/// @notice Assert that `
|
|
63
|
-
/// @param
|
|
64
|
-
/// @return account The same `
|
|
65
|
-
function
|
|
66
|
-
if (!
|
|
67
|
-
return
|
|
74
|
+
/// @notice Assert that `value` is an opaque account and return it unchanged.
|
|
75
|
+
/// @param value Account identifier to validate.
|
|
76
|
+
/// @return account The same `value` if it is opaque.
|
|
77
|
+
function opaque(bytes32 value) internal pure returns (bytes32 account) {
|
|
78
|
+
if (!Ids.isOpaque(value)) revert InvalidAccount();
|
|
79
|
+
return value;
|
|
68
80
|
}
|
|
69
81
|
|
|
70
|
-
/// @notice Assert that `
|
|
71
|
-
/// @param
|
|
72
|
-
/// @return account The same `
|
|
73
|
-
function
|
|
74
|
-
if (!
|
|
75
|
-
return
|
|
82
|
+
/// @notice Assert that `value` is an admin account and return it unchanged.
|
|
83
|
+
/// @param value Account identifier to validate.
|
|
84
|
+
/// @return account The same `value` if it is an admin account.
|
|
85
|
+
function admin(bytes32 value) internal pure returns (bytes32 account) {
|
|
86
|
+
if (!isAdmin(value)) revert InvalidAccount();
|
|
87
|
+
return value;
|
|
76
88
|
}
|
|
77
89
|
|
|
78
|
-
/// @notice Assert that `
|
|
79
|
-
/// @param
|
|
80
|
-
/// @return account The same `
|
|
81
|
-
function
|
|
82
|
-
if (!
|
|
83
|
-
return
|
|
90
|
+
/// @notice Assert that `value` is a guardian account and return it unchanged.
|
|
91
|
+
/// @param value Account identifier to validate.
|
|
92
|
+
/// @return account The same `value` if it is a guardian account.
|
|
93
|
+
function guardian(bytes32 value) internal pure returns (bytes32 account) {
|
|
94
|
+
if (!isGuardian(value)) revert InvalidAccount();
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/// @notice Assert that `value` is a user account and return it unchanged.
|
|
99
|
+
/// @param value Account identifier to validate.
|
|
100
|
+
/// @return account The same `value` if it is a user account.
|
|
101
|
+
function user(bytes32 value) internal pure returns (bytes32 account) {
|
|
102
|
+
if (!isUser(value)) revert InvalidAccount();
|
|
103
|
+
return value;
|
|
84
104
|
}
|
|
85
105
|
|
|
86
106
|
/// @notice Encode an EVM address as a chain-local admin account ID.
|
|
87
|
-
/// @param
|
|
107
|
+
/// @param account EVM address to embed.
|
|
88
108
|
/// @return Admin account ID bound to the current chain.
|
|
89
|
-
function toAdmin(address
|
|
90
|
-
return bytes32(toLocalBase(Admin) | (uint(uint160(
|
|
109
|
+
function toAdmin(address account) internal view returns (bytes32) {
|
|
110
|
+
return bytes32(toLocalBase(Admin) | (uint(uint160(account)) << 32));
|
|
91
111
|
}
|
|
92
112
|
|
|
93
113
|
/// @notice Encode an EVM address as a chain-local guardian account ID.
|
|
94
|
-
/// @param
|
|
114
|
+
/// @param account EVM address to embed.
|
|
95
115
|
/// @return Guardian account ID bound to the current chain.
|
|
96
|
-
function toGuardian(address
|
|
97
|
-
return bytes32(toLocalBase(Guardian) | (uint(uint160(
|
|
116
|
+
function toGuardian(address account) internal view returns (bytes32) {
|
|
117
|
+
return bytes32(toLocalBase(Guardian) | (uint(uint160(account)) << 32));
|
|
98
118
|
}
|
|
99
119
|
|
|
100
120
|
/// @notice Encode an EVM address as a chain-agnostic user account ID.
|
|
101
|
-
/// @param
|
|
121
|
+
/// @param account EVM address to embed.
|
|
102
122
|
/// @return User account ID without a chain binding.
|
|
103
|
-
function toUser(address
|
|
104
|
-
return bytes32(toUnspecifiedBase(User) | (uint(uint160(
|
|
123
|
+
function toUser(address account) internal pure returns (bytes32) {
|
|
124
|
+
return bytes32(toUnspecifiedBase(User) | (uint(uint160(account)) << 32));
|
|
105
125
|
}
|
|
106
126
|
|
|
107
|
-
/// @notice
|
|
108
|
-
/// @param
|
|
109
|
-
/// @return
|
|
110
|
-
function
|
|
111
|
-
|
|
112
|
-
revert InvalidAccount();
|
|
113
|
-
}
|
|
114
|
-
return account;
|
|
127
|
+
/// @notice Derive an opaque account ID from a keccak preimage.
|
|
128
|
+
/// @param preimage Preimage whose first byte is `0x01`.
|
|
129
|
+
/// @return account `0x00 || bytes31(keccak256(preimage))`.
|
|
130
|
+
function toKeccak(bytes memory preimage) internal pure returns (bytes32 account) {
|
|
131
|
+
return Ids.toKeccak(preimage);
|
|
115
132
|
}
|
|
116
133
|
|
|
117
|
-
/// @notice Assert that `account`
|
|
118
|
-
/// @param account
|
|
119
|
-
/// @
|
|
120
|
-
|
|
121
|
-
|
|
134
|
+
/// @notice Assert that `account` matches the opaque keccak ID for `preimage`.
|
|
135
|
+
/// @param account Opaque account ID to validate.
|
|
136
|
+
/// @param preimage Preimage whose first byte is `0x01`.
|
|
137
|
+
/// @return The same `account` value if it matches.
|
|
138
|
+
function matchKeccak(bytes32 account, bytes memory preimage) internal pure returns (bytes32) {
|
|
139
|
+
if (account != Ids.toKeccak(preimage)) revert InvalidAccount();
|
|
122
140
|
return account;
|
|
123
141
|
}
|
|
124
142
|
|
|
125
|
-
/// @notice Extract the
|
|
143
|
+
/// @notice Extract the address embedded in an EVM-family account ID.
|
|
126
144
|
/// Reverts if `account` is not an EVM-family account.
|
|
127
145
|
/// @param account EVM-family account ID.
|
|
128
|
-
/// @return Embedded
|
|
129
|
-
function
|
|
130
|
-
return address(uint160(uint(
|
|
146
|
+
/// @return Embedded address (bits [191:32] of the ID).
|
|
147
|
+
function addr(bytes32 account) internal pure returns (address) {
|
|
148
|
+
return ensureAddr(address(uint160(uint(evm(account)) >> 32)));
|
|
131
149
|
}
|
|
132
150
|
}
|
package/utils/Assets.sol
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
pragma solidity ^0.8.33;
|
|
3
3
|
|
|
4
4
|
import {Layout} from "./Layout.sol";
|
|
5
|
-
import {
|
|
5
|
+
import {Ids} from "./Ids.sol";
|
|
6
|
+
import {ensureAddr, isFamily, matchesBase, toLocalBase} from "./Utils.sol";
|
|
6
7
|
|
|
7
8
|
/// @title Assets
|
|
8
9
|
/// @notice Encoding and decoding helpers for 256-bit asset identifiers.
|
|
@@ -10,8 +11,12 @@ import {matchesBase, toLocalBase} from "./Utils.sol";
|
|
|
10
11
|
/// Asset IDs embed a 4-byte type tag in bits [255:224]:
|
|
11
12
|
/// - `Native` - native chain coin/token; no address payload
|
|
12
13
|
/// - `Erc20` - ERC-20 token; contract address in bits [191:32]
|
|
13
|
-
///
|
|
14
|
-
///
|
|
14
|
+
///
|
|
15
|
+
/// If the first byte is zero, the asset is an opaque
|
|
16
|
+
/// `0x00 || bytes31(hash)` ID. The full asset metadata must be supplied by
|
|
17
|
+
/// lookup or witness data when native token handling needs it.
|
|
18
|
+
///
|
|
19
|
+
/// The helpers in this library validate and deconstruct structured asset IDs.
|
|
15
20
|
///
|
|
16
21
|
/// All asset IDs are chain-local (include `block.chainid` in bits [223:192]).
|
|
17
22
|
library Assets {
|
|
@@ -20,28 +25,21 @@ library Assets {
|
|
|
20
25
|
/// @dev Thrown when an asset is not authorized for the requested operation.
|
|
21
26
|
error UnauthorizedAsset();
|
|
22
27
|
|
|
28
|
+
/// @dev 24-bit family tag shared by all EVM-backed asset types.
|
|
29
|
+
uint24 constant Family = (uint24(Layout.Evm) << 8) | uint24(Layout.Asset);
|
|
23
30
|
/// @dev Full 4-byte type prefix for the native chain coin/token asset.
|
|
24
|
-
uint32 constant Native = (uint32(Layout.
|
|
31
|
+
uint32 constant Native = (uint32(Layout.Evm) << 16) | (uint32(Layout.Asset) << 8) | uint32(Layout.Native);
|
|
25
32
|
/// @dev Full 4-byte type prefix for ERC-20 assets.
|
|
26
|
-
uint32 constant Erc20 = (uint32(Layout.
|
|
27
|
-
/// @dev Full 4-byte type prefix for ERC-721 assets.
|
|
28
|
-
uint32 constant Erc721 = (uint32(Layout.Evm64) << 16) | (uint32(Layout.Asset) << 8) | uint32(Layout.Erc721);
|
|
29
|
-
/// @dev Full 4-byte type prefix for ERC-1155 assets.
|
|
30
|
-
uint32 constant Erc1155 = (uint32(Layout.Evm64) << 16) | (uint32(Layout.Asset) << 8) | uint32(Layout.Erc1155);
|
|
31
|
-
|
|
32
|
-
/// @notice Return true if `asset` uses the Asset category tag in the type field.
|
|
33
|
-
function isAsset(bytes32 asset) internal pure returns (bool) {
|
|
34
|
-
return uint8(uint(asset) >> 232) == Layout.Asset;
|
|
35
|
-
}
|
|
33
|
+
uint32 constant Erc20 = (uint32(Layout.Evm) << 16) | (uint32(Layout.Asset) << 8) | uint32(Layout.Erc20);
|
|
36
34
|
|
|
37
|
-
/// @notice Return true if `asset`
|
|
38
|
-
function
|
|
39
|
-
return
|
|
35
|
+
/// @notice Return true if `asset` belongs to the EVM asset family.
|
|
36
|
+
function isEvm(bytes32 asset) internal pure returns (bool) {
|
|
37
|
+
return isFamily(uint(asset), Family);
|
|
40
38
|
}
|
|
41
39
|
|
|
42
|
-
/// @notice Return true if `asset`
|
|
43
|
-
function
|
|
44
|
-
return
|
|
40
|
+
/// @notice Return true if `asset` is opaque.
|
|
41
|
+
function isOpaque(bytes32 asset) internal pure returns (bool) {
|
|
42
|
+
return Ids.isOpaque(asset);
|
|
45
43
|
}
|
|
46
44
|
|
|
47
45
|
/// @notice Return true if `asset` is the local native chain coin/token asset.
|
|
@@ -54,46 +52,36 @@ library Assets {
|
|
|
54
52
|
return matchesBase(asset, toLocalBase(Erc20));
|
|
55
53
|
}
|
|
56
54
|
|
|
57
|
-
/// @notice
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
function isErc1155(bytes32 asset) internal view returns (bool) {
|
|
64
|
-
return matchesBase(asset, toLocalBase(Erc1155));
|
|
55
|
+
/// @notice Assert that `value` belongs to the EVM asset family and return it unchanged.
|
|
56
|
+
/// @param value Asset identifier to validate.
|
|
57
|
+
/// @return asset The same `value` if it is an EVM asset.
|
|
58
|
+
function evm(bytes32 value) internal pure returns (bytes32 asset) {
|
|
59
|
+
if (!isEvm(value)) revert InvalidAsset();
|
|
60
|
+
return value;
|
|
65
61
|
}
|
|
66
62
|
|
|
67
|
-
/// @notice Assert that `
|
|
68
|
-
/// @param
|
|
69
|
-
/// @return asset The same `
|
|
70
|
-
function
|
|
71
|
-
if (!
|
|
72
|
-
return
|
|
63
|
+
/// @notice Assert that `value` is an opaque asset and return it unchanged.
|
|
64
|
+
/// @param value Asset identifier to validate.
|
|
65
|
+
/// @return asset The same `value` if it is opaque.
|
|
66
|
+
function opaque(bytes32 value) internal pure returns (bytes32 asset) {
|
|
67
|
+
if (!Ids.isOpaque(value)) revert InvalidAsset();
|
|
68
|
+
return value;
|
|
73
69
|
}
|
|
74
70
|
|
|
75
|
-
/// @notice Assert that `
|
|
76
|
-
/// @param
|
|
77
|
-
/// @return asset The same `
|
|
78
|
-
function
|
|
79
|
-
if (!
|
|
80
|
-
return
|
|
71
|
+
/// @notice Assert that `value` is the local native chain coin/token asset and return it unchanged.
|
|
72
|
+
/// @param value Asset identifier to validate.
|
|
73
|
+
/// @return asset The same `value` if it is the local native asset.
|
|
74
|
+
function native(bytes32 value) internal view returns (bytes32 asset) {
|
|
75
|
+
if (!isNative(value)) revert InvalidAsset();
|
|
76
|
+
return value;
|
|
81
77
|
}
|
|
82
78
|
|
|
83
|
-
/// @notice Assert that `
|
|
84
|
-
/// @param
|
|
85
|
-
/// @return asset The same `
|
|
86
|
-
function
|
|
87
|
-
if (!
|
|
88
|
-
return
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/// @notice Assert that `input` is a local ERC-1155 asset and return it unchanged.
|
|
92
|
-
/// @param input Asset identifier to validate.
|
|
93
|
-
/// @return asset The same `input` if it is a local ERC-1155 asset.
|
|
94
|
-
function erc1155(bytes32 input) internal view returns (bytes32 asset) {
|
|
95
|
-
if (!isErc1155(input)) revert InvalidAsset();
|
|
96
|
-
return input;
|
|
79
|
+
/// @notice Assert that `value` is a local ERC-20 asset and return it unchanged.
|
|
80
|
+
/// @param value Asset identifier to validate.
|
|
81
|
+
/// @return asset The same `value` if it is a local ERC-20 asset.
|
|
82
|
+
function erc20(bytes32 value) internal view returns (bytes32 asset) {
|
|
83
|
+
if (!isErc20(value)) revert InvalidAsset();
|
|
84
|
+
return value;
|
|
97
85
|
}
|
|
98
86
|
|
|
99
87
|
/// @notice Create a chain-local native coin/token asset ID.
|
|
@@ -109,18 +97,20 @@ library Assets {
|
|
|
109
97
|
return bytes32(toLocalBase(Erc20) | (uint(uint160(addr)) << 32));
|
|
110
98
|
}
|
|
111
99
|
|
|
112
|
-
/// @notice
|
|
113
|
-
/// @param
|
|
114
|
-
/// @return
|
|
115
|
-
function
|
|
116
|
-
return
|
|
100
|
+
/// @notice Derive an opaque asset ID from a keccak preimage.
|
|
101
|
+
/// @param preimage Preimage whose first byte is `0x01`.
|
|
102
|
+
/// @return asset `0x00 || bytes31(keccak256(preimage))`.
|
|
103
|
+
function toKeccak(bytes memory preimage) internal pure returns (bytes32 asset) {
|
|
104
|
+
return Ids.toKeccak(preimage);
|
|
117
105
|
}
|
|
118
106
|
|
|
119
|
-
/// @notice
|
|
120
|
-
/// @param
|
|
121
|
-
/// @
|
|
122
|
-
|
|
123
|
-
|
|
107
|
+
/// @notice Assert that `asset` matches the opaque keccak ID for `preimage`.
|
|
108
|
+
/// @param asset Opaque asset ID to validate.
|
|
109
|
+
/// @param preimage Preimage whose first byte is `0x01`.
|
|
110
|
+
/// @return The same `asset` value if it matches.
|
|
111
|
+
function matchKeccak(bytes32 asset, bytes memory preimage) internal pure returns (bytes32) {
|
|
112
|
+
if (asset != Ids.toKeccak(preimage)) revert InvalidAsset();
|
|
113
|
+
return asset;
|
|
124
114
|
}
|
|
125
115
|
|
|
126
116
|
/// @notice Extract the ERC-20 contract address from an asset ID.
|
|
@@ -128,7 +118,7 @@ library Assets {
|
|
|
128
118
|
/// @param asset ERC-20 asset identifier.
|
|
129
119
|
/// @return Token contract address embedded in bits [191:32].
|
|
130
120
|
function erc20Addr(bytes32 asset) internal view returns (address) {
|
|
131
|
-
return address(uint160(uint(erc20(asset)) >> 32));
|
|
121
|
+
return ensureAddr(address(uint160(uint(erc20(asset)) >> 32)));
|
|
132
122
|
}
|
|
133
123
|
|
|
134
124
|
/// @notice Assert that `asset` is a local ERC-20 for `token` and return it unchanged.
|
|
@@ -141,56 +131,6 @@ library Assets {
|
|
|
141
131
|
return asset;
|
|
142
132
|
}
|
|
143
133
|
|
|
144
|
-
/// @notice Extract the ERC-721 collection address from an asset ID.
|
|
145
|
-
/// Reverts if `asset` is not a local ERC-721 asset.
|
|
146
|
-
/// @param asset ERC-721 asset identifier.
|
|
147
|
-
/// @return Collection contract address embedded in bits [191:32].
|
|
148
|
-
function erc721Collection(bytes32 asset) internal view returns (address) {
|
|
149
|
-
return address(uint160(uint(erc721(asset)) >> 32));
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/// @notice Assert that `asset` is a local ERC-721 for `collection` and return it unchanged.
|
|
153
|
-
/// Reverts if `asset` is not a local ERC-721 asset or if its collection address differs.
|
|
154
|
-
/// @param asset ERC-721 asset identifier.
|
|
155
|
-
/// @param collection Expected ERC-721 collection address.
|
|
156
|
-
/// @return The same `asset` value if valid.
|
|
157
|
-
function matchErc721(bytes32 asset, address collection) internal view returns (bytes32) {
|
|
158
|
-
if (erc721Collection(asset) != collection) revert InvalidAsset();
|
|
159
|
-
return asset;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/// @notice Extract the ERC-1155 collection address from an asset ID.
|
|
163
|
-
/// Reverts if `asset` is not a local ERC-1155 asset.
|
|
164
|
-
/// @param asset ERC-1155 asset identifier.
|
|
165
|
-
/// @return Collection contract address embedded in bits [191:32].
|
|
166
|
-
function erc1155Collection(bytes32 asset) internal view returns (address) {
|
|
167
|
-
return address(uint160(uint(erc1155(asset)) >> 32));
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/// @notice Assert that `asset` is a local ERC-1155 for `collection` and return it unchanged.
|
|
171
|
-
/// Reverts if `asset` is not a local ERC-1155 asset or if its collection address differs.
|
|
172
|
-
/// @param asset ERC-1155 asset identifier.
|
|
173
|
-
/// @param collection Expected ERC-1155 collection address.
|
|
174
|
-
/// @return The same `asset` value if valid.
|
|
175
|
-
function matchErc1155(bytes32 asset, address collection) internal view returns (bytes32) {
|
|
176
|
-
if (erc1155Collection(asset) != collection) revert InvalidAsset();
|
|
177
|
-
return asset;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/// @notice Derive a storage slot for an (asset, meta) pair.
|
|
181
|
-
/// For 32-byte assets (no meta), the slot is the asset ID itself.
|
|
182
|
-
/// For assets with metadata (e.g. ERC-721 or ERC-1155 token IDs), the slot is
|
|
183
|
-
/// `keccak256(asset ++ meta)`.
|
|
184
|
-
/// Reverts only if `asset` is zero.
|
|
185
|
-
/// For 32-byte assets, `meta` is ignored and does not affect the derived slot.
|
|
186
|
-
/// @param asset Asset identifier.
|
|
187
|
-
/// @param meta Asset metadata slot (e.g. token ID context).
|
|
188
|
-
/// @return Storage slot for the (asset, meta) combination.
|
|
189
|
-
function slot(bytes32 asset, bytes32 meta) internal pure returns (bytes32) {
|
|
190
|
-
if (is32(asset)) return asset;
|
|
191
|
-
if (meta == 0 || !is64(asset)) revert InvalidAsset();
|
|
192
|
-
return keccak256(bytes.concat(asset, meta));
|
|
193
|
-
}
|
|
194
134
|
}
|
|
195
135
|
|
|
196
136
|
/// @title Amounts
|