@rootzero/contracts 1.3.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.
Files changed (62) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/Endpoints.sol +4 -3
  3. package/Events.sol +3 -3
  4. package/README.md +58 -31
  5. package/Utils.sol +4 -3
  6. package/blocks/Cursors.sol +83 -143
  7. package/blocks/Keys.sol +15 -15
  8. package/blocks/Schema.sol +27 -28
  9. package/blocks/Writers.sol +26 -33
  10. package/commands/Base.sol +2 -2
  11. package/commands/Burn.sol +3 -4
  12. package/commands/Credit.sol +3 -4
  13. package/commands/Debit.sol +4 -5
  14. package/commands/Deposit.sol +8 -10
  15. package/commands/Payout.sol +3 -6
  16. package/commands/Withdraw.sol +3 -4
  17. package/commands/admin/AllowAssets.sol +5 -6
  18. package/commands/admin/Allowance.sol +3 -4
  19. package/commands/admin/DenyAssets.sol +5 -6
  20. package/commands/admin/Execute.sol +2 -2
  21. package/core/Access.sol +2 -2
  22. package/core/Balances.sol +10 -11
  23. package/core/Calls.sol +7 -7
  24. package/core/Host.sol +2 -2
  25. package/core/Runtime.sol +3 -3
  26. package/core/Types.sol +0 -14
  27. package/docs/Schema.md +29 -10
  28. package/events/Asset.sol +17 -3
  29. package/events/Balance.sol +2 -3
  30. package/events/Commander.sol +19 -0
  31. package/events/Labeled.sol +6 -6
  32. package/events/Locked.sol +2 -3
  33. package/events/Position.sol +2 -3
  34. package/events/Received.sol +2 -3
  35. package/events/Route.sol +18 -0
  36. package/events/Spent.sol +2 -3
  37. package/events/Unlocked.sol +2 -3
  38. package/guards/Base.sol +4 -4
  39. package/package.json +1 -1
  40. package/peer/AllowAssets.sol +3 -3
  41. package/peer/Allowance.sol +2 -2
  42. package/peer/Base.sol +4 -4
  43. package/peer/Credit.sol +10 -10
  44. package/peer/Debit.sol +10 -10
  45. package/peer/DenyAssets.sol +3 -3
  46. package/peer/Recover.sol +51 -0
  47. package/peer/Redeem.sol +48 -0
  48. package/peer/Settle.sol +3 -3
  49. package/queries/Assets.sol +7 -8
  50. package/queries/Balances.sol +8 -9
  51. package/queries/Base.sol +4 -4
  52. package/queries/Positions.sol +4 -6
  53. package/utils/Accounts.sol +76 -58
  54. package/utils/Actions.sol +1 -0
  55. package/utils/Assets.sol +55 -115
  56. package/utils/Ids.sol +33 -233
  57. package/utils/Layout.sol +11 -17
  58. package/utils/Nodes.sol +263 -0
  59. package/utils/Utils.sol +9 -24
  60. package/events/Chain.sol +0 -19
  61. package/events/Transfer.sol +0 -22
  62. package/peer/BalancePull.sol +0 -49
@@ -12,7 +12,7 @@ interface IPeerDenyAssets {
12
12
  }
13
13
 
14
14
  /// @title PeerDenyAssets
15
- /// @notice Peer that blocks a list of (asset, meta) pairs on behalf of a peer host.
15
+ /// @notice Peer that blocks a list of assets on behalf of a peer host.
16
16
  /// Each ASSET block in the request calls `denyAsset`. Restricted to trusted peers.
17
17
  abstract contract PeerDenyAssets is PeerBase, DenyAssetsHook, IPeerDenyAssets {
18
18
  uint internal immutable peerDenyAssetsId = peerId(this.peerDenyAssets.selector);
@@ -29,8 +29,8 @@ abstract contract PeerDenyAssets is PeerBase, DenyAssetsHook, IPeerDenyAssets {
29
29
  (Cur memory assets, , ) = Cursors.init(request, 1);
30
30
 
31
31
  while (assets.i < assets.len) {
32
- (bytes32 asset, bytes32 meta) = assets.unpackAsset();
33
- denyAsset(asset, meta);
32
+ bytes32 asset = assets.unpackAsset();
33
+ denyAsset(asset);
34
34
  }
35
35
 
36
36
  assets.complete();
@@ -0,0 +1,51 @@
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 IPeerRecover {
10
+ function peerRecover(bytes calldata request) external returns (bytes memory);
11
+ }
12
+
13
+ abstract contract RecoverHook {
14
+ /// @notice Override to recover one incoming pipe context from a peer host.
15
+ /// @dev Implementations may refund, retry, compensate, reconcile, or otherwise
16
+ /// handle the supplied state and remaining steps.
17
+ /// @param account Account identifier for the recovery context.
18
+ /// @param state Embedded recovery state block stream.
19
+ /// @param steps Embedded recovery step block stream.
20
+ function recover(bytes32 account, bytes calldata state, bytes calldata steps) internal virtual;
21
+ }
22
+
23
+ /// @title PeerRecover
24
+ /// @notice Peer endpoint for recovering an interrupted or failed pipe context.
25
+ /// @dev Commonly used when a cross-chain pipe fails at its destination, but the
26
+ /// hook is host-defined and may implement any recovery strategy. Each PIPE
27
+ /// block carries chain resources plus a CONTEXT block; the nested context
28
+ /// is passed to the recovery hook and resources are not allocated.
29
+ abstract contract PeerRecover is PeerBase, RecoverHook, IPeerRecover {
30
+ uint internal immutable peerRecoverId = peerId(this.peerRecover.selector);
31
+
32
+ constructor() {
33
+ emit Peer(host, peerRecoverId, "1:0", Schemas.Pipe, "", false);
34
+ emit Labeled(peerRecoverId, bytes32(0), "peerRecover");
35
+ }
36
+
37
+ /// @notice Forward peer-supplied recovery pipes to the host-defined recovery hook.
38
+ /// @param request PIPE block stream supplied by the trusted peer.
39
+ /// @return Empty response bytes.
40
+ function peerRecover(bytes calldata request) external onlyPeer returns (bytes memory) {
41
+ (Cur memory input, , ) = Cursors.init(request, 1);
42
+
43
+ while (input.i < input.len) {
44
+ (, bytes32 account, bytes calldata state, bytes calldata steps) = input.unpackPipe();
45
+ recover(account, state, steps);
46
+ }
47
+
48
+ input.complete();
49
+ return "";
50
+ }
51
+ }
@@ -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, bytes32 meta, uint amount) = state.unpackTransaction();
34
- if (from != 0) debitAccount(from, asset, meta, amount);
35
- if (to != 0) creditAccount(to, asset, meta, amount);
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();
@@ -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 tuple.
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, bytes32 meta) internal view virtual returns (uint status);
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 `(asset, meta)` tuples.
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 `(asset, meta)` tuples.
33
- /// @param request Block-stream request consisting of `#asset { bytes32 asset, bytes32 meta }` blocks.
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
- (bytes32 asset, bytes32 meta) = query.unpackAsset();
41
- uint status = assetStatus(asset, meta);
39
+ bytes32 asset = query.unpackAsset();
40
+ uint status = assetStatus(asset);
42
41
  response.appendStatus(status);
43
42
  }
44
43
 
@@ -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, bytes32 meta) internal view virtual returns (uint amount);
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, meta)` tuples.
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, meta)` tuples.
33
- /// @param request Block-stream request consisting of `accountAsset(account, asset, meta)*`.
34
- /// @return Block-stream response containing one `accountAmount(account, asset, meta, amount)` block per request block.
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, bytes32 meta) = query.unpackAccountAsset();
41
- uint amount = getBalance(account, asset, meta);
42
- response.appendAccountAmount(account, asset, meta, amount);
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 { Ids } from "../utils/Ids.sol";
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 `Ids.querySelector(target)`.
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 = Ids.querySelector(target);
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 Ids.toQuery(selector, address(this));
32
+ return Nodes.toQuery(selector, address(this));
33
33
  }
34
34
  }
@@ -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, meta)` tuples.
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, meta)*`.
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, bytes32 meta) = query.unpackAccountAsset();
49
- appendPosition(account, asset, meta, response);
46
+ (bytes32 account, bytes32 asset) = query.unpackAccountAsset();
47
+ appendPosition(account, asset, response);
50
48
  }
51
49
 
52
50
  query.complete();
@@ -2,7 +2,8 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import {Layout} from "./Layout.sol";
5
- import {isFamily, toLocalBase, toUnspecifiedBase} from "./Utils.sol";
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.Evm32) << 8) | uint24(Layout.Account);
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.Evm32) << 16) | (uint32(Layout.Account) << 8) | uint32(Layout.Admin);
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.Evm32) << 16) | (uint32(Layout.Account) << 8) | uint32(Layout.Guardian);
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.Evm32) << 16) | (uint32(Layout.Account) << 8) | uint32(Layout.User);
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` uses the Account category tag in the type field.
35
- function isAccount(bytes32 account) internal pure returns (bool) {
36
- return uint8(uint(account) >> 232) == Layout.Account;
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 `input` is an account and return it unchanged.
55
- /// @param input Account identifier to validate.
56
- /// @return account The same `input` if it is an account.
57
- function ensure(bytes32 input) internal pure returns (bytes32 account) {
58
- if (!isAccount(input)) revert InvalidAccount();
59
- return input;
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 `input` is an admin account and return it unchanged.
63
- /// @param input Account identifier to validate.
64
- /// @return account The same `input` if it is an admin account.
65
- function admin(bytes32 input) internal pure returns (bytes32 account) {
66
- if (!isAdmin(input)) revert InvalidAccount();
67
- return input;
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 `input` is a guardian account and return it unchanged.
71
- /// @param input Account identifier to validate.
72
- /// @return account The same `input` if it is a guardian account.
73
- function guardian(bytes32 input) internal pure returns (bytes32 account) {
74
- if (!isGuardian(input)) revert InvalidAccount();
75
- return input;
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 `input` is a user account and return it unchanged.
79
- /// @param input Account identifier to validate.
80
- /// @return account The same `input` if it is a user account.
81
- function user(bytes32 input) internal pure returns (bytes32 account) {
82
- if (!isUser(input)) revert InvalidAccount();
83
- return input;
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 addr EVM address to embed.
107
+ /// @param account EVM address to embed.
88
108
  /// @return Admin account ID bound to the current chain.
89
- function toAdmin(address addr) internal view returns (bytes32) {
90
- return bytes32(toLocalBase(Admin) | (uint(uint160(addr)) << 32));
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 addr EVM address to embed.
114
+ /// @param account EVM address to embed.
95
115
  /// @return Guardian account ID bound to the current chain.
96
- function toGuardian(address addr) internal view returns (bytes32) {
97
- return bytes32(toLocalBase(Guardian) | (uint(uint160(addr)) << 32));
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 addr EVM address to embed.
121
+ /// @param account EVM address to embed.
102
122
  /// @return User account ID without a chain binding.
103
- function toUser(address addr) internal pure returns (bytes32) {
104
- return bytes32(toUnspecifiedBase(User) | (uint(uint160(addr)) << 32));
123
+ function toUser(address account) internal pure returns (bytes32) {
124
+ return bytes32(toUnspecifiedBase(User) | (uint(uint160(account)) << 32));
105
125
  }
106
126
 
107
- /// @notice Assert that `account` belongs to the EVM account family and return it unchanged.
108
- /// @param account Account ID to validate.
109
- /// @return The same `account` value if valid.
110
- function ensureEvm(bytes32 account) internal pure returns (bytes32) {
111
- if (!isFamily(uint(account), Family)) {
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` is not an admin account and return it unchanged.
118
- /// @param account Account ID to validate.
119
- /// @return The same `account` value if valid.
120
- function ensureNotAdmin(bytes32 account) internal pure returns (bytes32) {
121
- if (isAdmin(account)) revert InvalidAccount();
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 EVM address embedded in an EVM-family account ID.
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 EVM address (bits [191:32] of the ID).
129
- function addrEvm(bytes32 account) internal pure returns (address) {
130
- return address(uint160(uint(ensureEvm(account)) >> 32));
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/Actions.sol CHANGED
@@ -15,4 +15,5 @@ library Actions {
15
15
  uint32 constant Borrow = 10;
16
16
  uint32 constant Repay = 11;
17
17
  uint32 constant Liquidate = 12;
18
+ uint32 constant Refund = 13;
18
19
  }