@rootzero/contracts 0.8.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Commands.sol +17 -23
- package/Core.sol +3 -4
- package/Cursors.sol +4 -6
- package/Events.sol +3 -2
- package/Queries.sol +1 -1
- package/README.md +18 -19
- package/Utils.sol +3 -3
- package/blocks/Cursors.sol +1398 -0
- package/blocks/Keys.sol +57 -34
- package/blocks/Schema.sol +108 -126
- package/blocks/Writers.sol +476 -301
- package/commands/Base.sol +32 -22
- package/commands/Burn.sol +5 -5
- package/commands/Credit.sol +8 -8
- package/commands/Debit.sol +5 -5
- package/commands/Deposit.sol +13 -12
- package/commands/Pipe.sol +5 -5
- package/commands/Provision.sol +25 -54
- package/commands/Transfer.sol +11 -21
- package/commands/Withdraw.sol +7 -8
- package/commands/{admin → control}/AllowAssets.sol +9 -8
- package/commands/control/Allowance.sol +43 -0
- package/commands/{admin → control}/Authorize.sol +9 -8
- package/commands/{admin → control}/DenyAssets.sol +9 -8
- package/commands/{admin → control}/Destroy.sol +8 -7
- package/commands/control/Execute.sol +39 -0
- package/commands/{admin → control}/Init.sol +8 -7
- package/commands/{admin → control}/Unauthorize.sol +9 -8
- package/core/Access.sol +40 -36
- package/core/Balances.sol +17 -18
- package/core/{Operation.sol → Calls.sol} +5 -8
- package/core/{CursorBase.sol → Context.sol} +11 -5
- package/core/Host.sol +9 -9
- package/core/Types.sol +86 -0
- package/docs/GETTING_STARTED.md +37 -29
- package/events/Asset.sol +1 -1
- package/events/Command.sol +10 -10
- package/events/Control.sol +31 -0
- package/events/Deposit.sol +3 -4
- package/events/Listing.sol +1 -1
- package/events/Position.sol +21 -0
- package/events/Query.sol +5 -5
- package/events/Remote.sol +24 -0
- package/events/Withdraw.sol +2 -3
- package/package.json +1 -1
- package/queries/Assets.sol +8 -8
- package/queries/Balances.sol +11 -11
- package/queries/Base.sol +2 -3
- package/queries/Positions.sol +25 -19
- package/remote/AllowAssets.sol +39 -0
- package/remote/Allowance.sol +36 -0
- package/remote/AssetPull.sol +44 -0
- package/remote/Base.sol +40 -0
- package/remote/DenyAssets.sol +39 -0
- package/remote/Settle.sol +33 -0
- package/utils/Accounts.sol +14 -13
- package/utils/Assets.sol +77 -57
- package/utils/Ids.sol +34 -34
- package/utils/Layout.sol +4 -4
- package/utils/Utils.sol +10 -0
- package/blocks/cursors/Core.sol +0 -1121
- package/blocks/cursors/Erc1155.sol +0 -149
- package/blocks/cursors/Erc20.sol +0 -130
- package/blocks/cursors/Erc721.sol +0 -66
- package/commands/Create.sol +0 -44
- package/commands/Remove.sol +0 -44
- package/commands/Settle.sol +0 -38
- package/commands/Stake.sol +0 -49
- package/commands/Supply.sol +0 -43
- package/commands/admin/Allocate.sol +0 -43
- package/commands/admin/Relocate.sol +0 -43
- package/core/HostBound.sol +0 -14
- package/events/Erc721.sol +0 -20
- package/events/Peer.sol +0 -24
- package/peer/AllowAssets.sol +0 -39
- package/peer/AssetPull.sol +0 -44
- package/peer/Base.sol +0 -36
- package/peer/DenyAssets.sol +0 -39
- package/peer/Pull.sol +0 -41
- package/peer/Push.sol +0 -47
- package/peer/Settle.sol +0 -34
- package/utils/State.sol +0 -22
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
|
+
pragma solidity ^0.8.33;
|
|
3
|
+
|
|
4
|
+
import {RemoteBase} from "./Base.sol";
|
|
5
|
+
import {AllowanceHook} from "../commands/control/Allowance.sol";
|
|
6
|
+
import {Cursors, Cur, Schemas} from "../Cursors.sol";
|
|
7
|
+
|
|
8
|
+
using Cursors for Cur;
|
|
9
|
+
|
|
10
|
+
string constant NAME = "remoteAllowance";
|
|
11
|
+
|
|
12
|
+
/// @title RemoteAllowance
|
|
13
|
+
/// @notice Remote that lets a trusted remote host request or refresh its own allowance.
|
|
14
|
+
/// Each AMOUNT block in the request is scoped to the remote host and passed to the
|
|
15
|
+
/// shared allowance hook as a host-scoped allowance. Restricted to trusted remotes.
|
|
16
|
+
abstract contract RemoteAllowance is RemoteBase, AllowanceHook {
|
|
17
|
+
uint internal immutable remoteAllowanceId = remoteId(NAME);
|
|
18
|
+
|
|
19
|
+
constructor() {
|
|
20
|
+
emit Remote(host, remoteAllowanceId, NAME, Schemas.Amount, false);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// @notice Execute the allowance remote call.
|
|
24
|
+
function remoteAllowance(bytes calldata request) external onlyRemote returns (bytes memory) {
|
|
25
|
+
(Cur memory amounts, , ) = cursor(request, 1);
|
|
26
|
+
uint remote = caller();
|
|
27
|
+
|
|
28
|
+
while (amounts.i < amounts.bound) {
|
|
29
|
+
(bytes32 asset, bytes32 meta, uint amount) = amounts.unpackAmount();
|
|
30
|
+
allowance(remote, asset, meta, amount);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
amounts.complete();
|
|
34
|
+
return "";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
|
+
pragma solidity ^0.8.33;
|
|
3
|
+
|
|
4
|
+
import {RemoteBase} from "./Base.sol";
|
|
5
|
+
import {Cursors, Cur, Schemas} from "../Cursors.sol";
|
|
6
|
+
|
|
7
|
+
string constant NAME = "remoteAssetPull";
|
|
8
|
+
|
|
9
|
+
using Cursors for Cur;
|
|
10
|
+
|
|
11
|
+
abstract contract AssetPullHook {
|
|
12
|
+
/// @notice Override to process one incoming amount-based asset pull request from a remote host.
|
|
13
|
+
/// @param remote Remote host node ID for this request.
|
|
14
|
+
/// @param asset Requested asset identifier.
|
|
15
|
+
/// @param meta Requested asset metadata slot.
|
|
16
|
+
/// @param amount Requested amount in the asset's native units.
|
|
17
|
+
function assetPull(uint remote, bytes32 asset, bytes32 meta, uint amount) internal virtual;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/// @title RemoteAssetPull
|
|
21
|
+
/// @notice Remote that pulls requested asset amounts from a remote host into this one.
|
|
22
|
+
/// Each AMOUNT block in the request calls `assetPull(remote, asset, meta, amount)`.
|
|
23
|
+
/// Restricted to trusted remotes.
|
|
24
|
+
abstract contract RemoteAssetPull is RemoteBase, AssetPullHook {
|
|
25
|
+
uint internal immutable remoteAssetPullId = remoteId(NAME);
|
|
26
|
+
|
|
27
|
+
constructor() {
|
|
28
|
+
emit Remote(host, remoteAssetPullId, NAME, Schemas.Amount, false);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// @notice Execute the asset-pull remote call.
|
|
32
|
+
function remoteAssetPull(bytes calldata request) external onlyRemote returns (bytes memory) {
|
|
33
|
+
(Cur memory assets, , ) = cursor(request, 1);
|
|
34
|
+
uint remote = caller();
|
|
35
|
+
|
|
36
|
+
while (assets.i < assets.bound) {
|
|
37
|
+
(bytes32 asset, bytes32 meta, uint amount) = assets.unpackAmount();
|
|
38
|
+
assetPull(remote, asset, meta, amount);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
assets.complete();
|
|
42
|
+
return "";
|
|
43
|
+
}
|
|
44
|
+
}
|
package/remote/Base.sol
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
|
+
pragma solidity ^0.8.33;
|
|
3
|
+
|
|
4
|
+
import { NodeCalls } from "../core/Calls.sol";
|
|
5
|
+
import { RemoteEvent } from "../events/Remote.sol";
|
|
6
|
+
import { Ids, Selectors } from "../utils/Ids.sol";
|
|
7
|
+
|
|
8
|
+
/// @notice ABI-encode a remote call from a target remote ID and request block stream.
|
|
9
|
+
/// @dev Derives the function selector from `target` via `Ids.remoteSelector(target)`.
|
|
10
|
+
/// Reverts if `target` is not a valid remote ID.
|
|
11
|
+
/// @param target Destination remote node ID embedding the target selector.
|
|
12
|
+
/// @param request Input block stream for the remote invocation.
|
|
13
|
+
/// @return ABI-encoded calldata for the remote entry point.
|
|
14
|
+
function encodeRemoteCall(uint target, bytes calldata request) pure returns (bytes memory) {
|
|
15
|
+
bytes4 selector = Ids.remoteSelector(target);
|
|
16
|
+
return abi.encodeWithSelector(selector, request);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/// @title RemoteBase
|
|
20
|
+
/// @notice Abstract base for all rootzero remote contracts.
|
|
21
|
+
/// Remotes handle inter-host operations and asset allow/deny management
|
|
22
|
+
/// between cooperating hosts. Access is restricted to trusted callers via `onlyRemote`.
|
|
23
|
+
abstract contract RemoteBase is NodeCalls, RemoteEvent {
|
|
24
|
+
/// @dev Thrown when the commander attempts to call a remote entrypoint directly.
|
|
25
|
+
error CommanderNotAllowed();
|
|
26
|
+
|
|
27
|
+
/// @dev Restrict execution to trusted callers, excluding the commander.
|
|
28
|
+
modifier onlyRemote() {
|
|
29
|
+
if (msg.sender == commander) revert CommanderNotAllowed();
|
|
30
|
+
enforceCaller(msg.sender);
|
|
31
|
+
_;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// @notice Derive the deterministic node ID for a named remote on this contract.
|
|
35
|
+
/// @param name Remote function name (without argument list).
|
|
36
|
+
/// @return Remote node ID.
|
|
37
|
+
function remoteId(string memory name) internal view returns (uint) {
|
|
38
|
+
return Ids.toRemote(Selectors.remote(name), address(this));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
|
+
pragma solidity ^0.8.33;
|
|
3
|
+
|
|
4
|
+
import {RemoteBase} from "./Base.sol";
|
|
5
|
+
import {DenyAssetsHook} from "../commands/control/DenyAssets.sol";
|
|
6
|
+
import {Cursors, Cur, Schemas} from "../Cursors.sol";
|
|
7
|
+
|
|
8
|
+
using Cursors for Cur;
|
|
9
|
+
|
|
10
|
+
string constant NAME = "remoteDenyAssets";
|
|
11
|
+
|
|
12
|
+
/// @title RemoteDenyAssets
|
|
13
|
+
/// @notice Remote that blocks a list of (asset, meta) pairs on behalf of a remote host.
|
|
14
|
+
/// Each ASSET block in the request calls `denyAsset`. Restricted to trusted remotes.
|
|
15
|
+
abstract contract RemoteDenyAssets is RemoteBase, DenyAssetsHook {
|
|
16
|
+
uint internal immutable remoteDenyAssetsId = remoteId(NAME);
|
|
17
|
+
|
|
18
|
+
constructor() {
|
|
19
|
+
emit Remote(host, remoteDenyAssetsId, NAME, Schemas.Asset, false);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/// @notice Execute the deny-assets remote call.
|
|
23
|
+
function remoteDenyAssets(bytes calldata request) external onlyRemote returns (bytes memory) {
|
|
24
|
+
(Cur memory assets, , ) = cursor(request, 1);
|
|
25
|
+
|
|
26
|
+
while (assets.i < assets.bound) {
|
|
27
|
+
(bytes32 asset, bytes32 meta) = assets.unpackAsset();
|
|
28
|
+
denyAsset(asset, meta);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
assets.complete();
|
|
32
|
+
return "";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
|
+
pragma solidity ^0.8.33;
|
|
3
|
+
|
|
4
|
+
import { RemoteBase } from "./Base.sol";
|
|
5
|
+
import { TransferHook } from "../commands/Transfer.sol";
|
|
6
|
+
import { Cursors, Cur, Schemas } from "../Cursors.sol";
|
|
7
|
+
|
|
8
|
+
using Cursors for Cur;
|
|
9
|
+
|
|
10
|
+
string constant NAME = "remoteSettle";
|
|
11
|
+
|
|
12
|
+
/// @title RemoteSettle
|
|
13
|
+
/// @notice Remote that consumes remote-supplied TRANSACTION blocks through the shared transfer hook.
|
|
14
|
+
/// Each TRANSACTION block in the request calls `transfer(value)`. Restricted to trusted remotes.
|
|
15
|
+
abstract contract RemoteSettle is RemoteBase, TransferHook {
|
|
16
|
+
uint internal immutable remoteSettleId = remoteId(NAME);
|
|
17
|
+
|
|
18
|
+
constructor() {
|
|
19
|
+
emit Remote(host, remoteSettleId, NAME, Schemas.Transaction, false);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/// @notice Execute the remote-settle call.
|
|
23
|
+
function remoteSettle(bytes calldata request) external onlyRemote returns (bytes memory) {
|
|
24
|
+
(Cur memory state, , ) = cursor(request, 1);
|
|
25
|
+
|
|
26
|
+
while (state.i < state.bound) {
|
|
27
|
+
transfer(state.unpackTxValue());
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
state.complete();
|
|
31
|
+
return "";
|
|
32
|
+
}
|
|
33
|
+
}
|
package/utils/Accounts.sol
CHANGED
|
@@ -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
|
|
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
|
-
|
|
59
|
-
|
|
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
|
-
|
|
67
|
-
|
|
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
|
@@ -27,14 +27,71 @@ library Assets {
|
|
|
27
27
|
/// @dev Full 4-byte type prefix for ERC-1155 assets.
|
|
28
28
|
uint32 constant Erc1155 = (uint32(Layout.Evm64) << 16) | (uint32(Layout.Asset) << 8) | uint32(Layout.Erc1155);
|
|
29
29
|
|
|
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
|
+
|
|
30
35
|
/// @notice Return true if `asset` uses the 32-byte asset layout with no metadata identity (top byte is `0x20`).
|
|
31
36
|
function is32(bytes32 asset) internal pure returns (bool) {
|
|
32
|
-
return bytes1(asset) == 0x20;
|
|
37
|
+
return isAsset(asset) && bytes1(asset) == 0x20;
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
/// @notice Return true if `asset` uses the 64-byte asset layout with metadata-backed identity (top byte is `0x40`).
|
|
36
41
|
function is64(bytes32 asset) internal pure returns (bool) {
|
|
37
|
-
return bytes1(asset) == 0x40;
|
|
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;
|
|
38
95
|
}
|
|
39
96
|
|
|
40
97
|
/// @notice Create a chain-local native value asset ID.
|
|
@@ -64,52 +121,12 @@ library Assets {
|
|
|
64
121
|
return bytes32(toLocalBase(Erc1155) | (uint(uint160(collection)) << 32));
|
|
65
122
|
}
|
|
66
123
|
|
|
67
|
-
/// @notice Derive a storage key for an (asset, meta) pair.
|
|
68
|
-
/// For 32-byte EVM assets (no meta), the key is the asset ID itself.
|
|
69
|
-
/// For assets with metadata (e.g. ERC-721 or ERC-1155 token IDs), the key is
|
|
70
|
-
/// `keccak256(asset ++ meta)`.
|
|
71
|
-
/// Reverts only if `asset` is zero.
|
|
72
|
-
/// For 32-byte assets, `meta` is ignored and does not affect the derived key.
|
|
73
|
-
/// @param asset Asset identifier.
|
|
74
|
-
/// @param meta Asset metadata slot (e.g. token ID context).
|
|
75
|
-
/// @return Storage key for the (asset, meta) combination.
|
|
76
|
-
function key(bytes32 asset, bytes32 meta) internal pure returns (bytes32) {
|
|
77
|
-
if (asset == 0) revert InvalidAsset();
|
|
78
|
-
return bytes1(asset) == 0x20 ? asset : keccak256(bytes.concat(asset, meta));
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/// @notice Return true when two local ERC-20 assets are already in canonical token-address order.
|
|
82
|
-
/// Useful for pair-style integrations that require a stable token ordering
|
|
83
|
-
/// regardless of the caller's input order.
|
|
84
|
-
/// Reverts if either asset is not a local ERC-20 asset.
|
|
85
|
-
/// @param a First ERC-20 asset identifier.
|
|
86
|
-
/// @param b Second ERC-20 asset identifier.
|
|
87
|
-
/// @return ordered Whether `a`'s token address is lower than `b`'s token address.
|
|
88
|
-
function isSortedErc20(bytes32 a, bytes32 b) internal view returns (bool ordered) {
|
|
89
|
-
return erc20Addr(a) < erc20Addr(b);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/// @notice Extract the token addresses for two local ERC-20 assets and report whether they are already ordered.
|
|
93
|
-
/// The returned addresses preserve the original input order.
|
|
94
|
-
/// Reverts if either asset is not a local ERC-20 asset.
|
|
95
|
-
/// @param a First ERC-20 asset identifier.
|
|
96
|
-
/// @param b Second ERC-20 asset identifier.
|
|
97
|
-
/// @return addrA Token address extracted from `a`.
|
|
98
|
-
/// @return addrB Token address extracted from `b`.
|
|
99
|
-
/// @return ordered Whether `addrA` is lower than `addrB`.
|
|
100
|
-
function erc20Addrs(bytes32 a, bytes32 b) internal view returns (address addrA, address addrB, bool ordered) {
|
|
101
|
-
addrA = erc20Addr(a);
|
|
102
|
-
addrB = erc20Addr(b);
|
|
103
|
-
ordered = addrA < addrB;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
124
|
/// @notice Extract the ERC-20 contract address from an asset ID.
|
|
107
125
|
/// Reverts if `asset` is not a local ERC-20 asset.
|
|
108
126
|
/// @param asset ERC-20 asset identifier.
|
|
109
127
|
/// @return Token contract address embedded in bits [191:32].
|
|
110
128
|
function erc20Addr(bytes32 asset) internal view returns (address) {
|
|
111
|
-
|
|
112
|
-
return address(uint160(uint(asset) >> 32));
|
|
129
|
+
return address(uint160(uint(erc20(asset)) >> 32));
|
|
113
130
|
}
|
|
114
131
|
|
|
115
132
|
/// @notice Assert that `asset` is a local ERC-20 for `token` and return it unchanged.
|
|
@@ -127,8 +144,7 @@ library Assets {
|
|
|
127
144
|
/// @param asset ERC-721 asset identifier.
|
|
128
145
|
/// @return Collection contract address embedded in bits [191:32].
|
|
129
146
|
function erc721Collection(bytes32 asset) internal view returns (address) {
|
|
130
|
-
|
|
131
|
-
return address(uint160(uint(asset) >> 32));
|
|
147
|
+
return address(uint160(uint(erc721(asset)) >> 32));
|
|
132
148
|
}
|
|
133
149
|
|
|
134
150
|
/// @notice Assert that `asset` is a local ERC-721 for `collection` and return it unchanged.
|
|
@@ -146,8 +162,7 @@ library Assets {
|
|
|
146
162
|
/// @param asset ERC-1155 asset identifier.
|
|
147
163
|
/// @return Collection contract address embedded in bits [191:32].
|
|
148
164
|
function erc1155Collection(bytes32 asset) internal view returns (address) {
|
|
149
|
-
|
|
150
|
-
return address(uint160(uint(asset) >> 32));
|
|
165
|
+
return address(uint160(uint(erc1155(asset)) >> 32));
|
|
151
166
|
}
|
|
152
167
|
|
|
153
168
|
/// @notice Assert that `asset` is a local ERC-1155 for `collection` and return it unchanged.
|
|
@@ -159,6 +174,21 @@ library Assets {
|
|
|
159
174
|
if (erc1155Collection(asset) != collection) revert InvalidAsset();
|
|
160
175
|
return asset;
|
|
161
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));
|
|
191
|
+
}
|
|
162
192
|
}
|
|
163
193
|
|
|
164
194
|
/// @title Amounts
|
|
@@ -191,16 +221,6 @@ library Amounts {
|
|
|
191
221
|
return amount;
|
|
192
222
|
}
|
|
193
223
|
|
|
194
|
-
/// @notice Assert non-zero amount and derive the storage key for the (asset, meta) pair.
|
|
195
|
-
/// @param asset Asset identifier.
|
|
196
|
-
/// @param meta Asset metadata slot.
|
|
197
|
-
/// @param amount Amount to validate (must be non-zero).
|
|
198
|
-
/// @return key_ Storage key from `Assets.key(asset, meta)`.
|
|
199
|
-
function ensureKey(bytes32 asset, bytes32 meta, uint amount) internal pure returns (bytes32 key_) {
|
|
200
|
-
ensure(amount);
|
|
201
|
-
return Assets.key(asset, meta);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
224
|
/// @notice Clamp `available` to `[min, max]`.
|
|
205
225
|
/// Uses all of `available` if it does not exceed `max`; reverts if the result
|
|
206
226
|
/// would fall below `min`.
|
package/utils/Ids.sol
CHANGED
|
@@ -8,9 +8,9 @@ import {isLocalFamily, matchesBase, toLocalBase} from "./Utils.sol";
|
|
|
8
8
|
/// @notice Encoding and decoding helpers for 256-bit node identifiers.
|
|
9
9
|
///
|
|
10
10
|
/// Node IDs share a common layout:
|
|
11
|
-
/// - bits [255:224] — 4-byte type prefix (`Host`, `Command`, or `
|
|
11
|
+
/// - bits [255:224] — 4-byte type prefix (`Host`, `Command`, or `Remote`)
|
|
12
12
|
/// - bits [223:192] — current `block.chainid` (makes IDs chain-local)
|
|
13
|
-
/// - bits [191:160] — 4-byte ABI selector (commands and
|
|
13
|
+
/// - bits [191:160] — 4-byte ABI selector (commands and remotes only)
|
|
14
14
|
/// - bits [159:0] — 160-bit EVM contract address
|
|
15
15
|
library Ids {
|
|
16
16
|
/// @dev Thrown when an ID does not match the expected node type or chain.
|
|
@@ -22,8 +22,8 @@ library Ids {
|
|
|
22
22
|
uint32 constant Host = (uint32(Layout.Evm32) << 16) | (uint32(Layout.Node) << 8) | uint32(Layout.Host);
|
|
23
23
|
/// @dev Full 4-byte type prefix for command nodes.
|
|
24
24
|
uint32 constant Command = (uint32(Layout.Evm32) << 16) | (uint32(Layout.Node) << 8) | uint32(Layout.Command);
|
|
25
|
-
/// @dev Full 4-byte type prefix for
|
|
26
|
-
uint32 constant
|
|
25
|
+
/// @dev Full 4-byte type prefix for remote nodes.
|
|
26
|
+
uint32 constant Remote = (uint32(Layout.Evm32) << 16) | (uint32(Layout.Node) << 8) | uint32(Layout.Remote);
|
|
27
27
|
/// @dev Full 4-byte type prefix for query nodes.
|
|
28
28
|
uint32 constant Query = (uint32(Layout.Evm32) << 16) | (uint32(Layout.Node) << 8) | uint32(Layout.Query);
|
|
29
29
|
|
|
@@ -37,9 +37,9 @@ library Ids {
|
|
|
37
37
|
return uint32(id >> 224) == Command;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
/// @notice Return true if `id` is a
|
|
41
|
-
function
|
|
42
|
-
return uint32(id >> 224) ==
|
|
40
|
+
/// @notice Return true if `id` is a remote node ID.
|
|
41
|
+
function isRemote(uint id) internal pure returns (bool) {
|
|
42
|
+
return uint32(id >> 224) == Remote;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
/// @notice Return true if `id` is a query node ID.
|
|
@@ -63,26 +63,26 @@ library Ids {
|
|
|
63
63
|
return bytes4(uint32(id >> 160));
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
/// @notice Assert that `id` is a
|
|
66
|
+
/// @notice Assert that `id` is a remote ID and return it unchanged.
|
|
67
67
|
/// @param id Node ID to validate.
|
|
68
|
-
/// @return pid The same `id` value if it is a
|
|
69
|
-
function
|
|
70
|
-
if (!
|
|
68
|
+
/// @return pid The same `id` value if it is a remote.
|
|
69
|
+
function remote(uint id) internal pure returns (uint pid) {
|
|
70
|
+
if (!isRemote(id)) revert InvalidId();
|
|
71
71
|
return id;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
/// @notice Assert that `id` is a
|
|
74
|
+
/// @notice Assert that `id` is a remote ID and return its embedded ABI selector.
|
|
75
75
|
/// @param id Node ID to validate.
|
|
76
|
-
/// @return selector 4-byte
|
|
77
|
-
function
|
|
78
|
-
if (!
|
|
76
|
+
/// @return selector 4-byte remote selector stored in bits [191:160].
|
|
77
|
+
function remoteSelector(uint id) internal pure returns (bytes4 selector) {
|
|
78
|
+
if (!isRemote(id)) revert InvalidId();
|
|
79
79
|
return bytes4(uint32(id >> 160));
|
|
80
80
|
}
|
|
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
|
|
85
|
-
function query(uint id) internal pure returns (uint
|
|
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
|
}
|
|
@@ -121,12 +121,12 @@ library Ids {
|
|
|
121
121
|
return id;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
/// @notice Build a chain-local
|
|
125
|
-
/// @param selector 4-byte ABI selector of the
|
|
126
|
-
/// @param target
|
|
127
|
-
/// @return
|
|
128
|
-
function
|
|
129
|
-
uint id = toLocalBase(
|
|
124
|
+
/// @notice Build a chain-local remote ID for the given selector and contract.
|
|
125
|
+
/// @param selector 4-byte ABI selector of the remote entry point.
|
|
126
|
+
/// @param target Remote contract address.
|
|
127
|
+
/// @return Remote node ID embedding both the selector and address.
|
|
128
|
+
function toRemote(bytes4 selector, address target) internal view returns (uint) {
|
|
129
|
+
uint id = toLocalBase(Remote) | uint(uint160(target));
|
|
130
130
|
id |= uint(uint32(selector)) << 160;
|
|
131
131
|
return id;
|
|
132
132
|
}
|
|
@@ -143,7 +143,7 @@ library Ids {
|
|
|
143
143
|
|
|
144
144
|
/// @notice Extract the contract address from any local node ID.
|
|
145
145
|
/// Reverts if `id` does not belong to the local node family.
|
|
146
|
-
/// @param id Node ID (host, command, or
|
|
146
|
+
/// @param id Node ID (host, command, or remote).
|
|
147
147
|
/// @return Contract address in the lower 160 bits of `id`.
|
|
148
148
|
function nodeAddr(uint id) internal view returns (address) {
|
|
149
149
|
if (!isLocalFamily(id, Node)) revert InvalidId();
|
|
@@ -161,12 +161,12 @@ library Ids {
|
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
/// @title Selectors
|
|
164
|
-
/// @notice ABI-selector derivation helpers for command,
|
|
164
|
+
/// @notice ABI-selector derivation helpers for command, remote, and query dispatch.
|
|
165
165
|
library Selectors {
|
|
166
|
-
/// @dev ABI argument encoding for command entry points: `((
|
|
167
|
-
string constant CommandArgs = "((
|
|
168
|
-
/// @dev ABI argument encoding for
|
|
169
|
-
string constant
|
|
166
|
+
/// @dev ABI argument encoding for command entry points: `((bytes32,bytes,bytes))`.
|
|
167
|
+
string constant CommandArgs = "((bytes32,bytes,bytes))";
|
|
168
|
+
/// @dev ABI argument encoding for remote entry points: `(bytes)`.
|
|
169
|
+
string constant RemoteArgs = "(bytes)";
|
|
170
170
|
/// @dev ABI argument encoding for query entry points: `(bytes)`.
|
|
171
171
|
string constant QueryArgs = "(bytes)";
|
|
172
172
|
|
|
@@ -178,12 +178,12 @@ library Selectors {
|
|
|
178
178
|
return bytes4(keccak256(bytes.concat(bytes(name), bytes(CommandArgs))));
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
-
/// @notice Derive the 4-byte ABI selector for a named
|
|
182
|
-
/// The selector is `keccak256(name ++
|
|
183
|
-
/// @param name
|
|
181
|
+
/// @notice Derive the 4-byte ABI selector for a named remote.
|
|
182
|
+
/// The selector is `keccak256(name ++ RemoteArgs)[0:4]`.
|
|
183
|
+
/// @param name Remote function name (without arguments).
|
|
184
184
|
/// @return 4-byte selector.
|
|
185
|
-
function
|
|
186
|
-
return bytes4(keccak256(bytes.concat(bytes(name), bytes(
|
|
185
|
+
function remote(string memory name) internal pure returns (bytes4) {
|
|
186
|
+
return bytes4(keccak256(bytes.concat(bytes(name), bytes(RemoteArgs))));
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
/// @notice Derive the 4-byte ABI selector for a named query.
|
package/utils/Layout.sol
CHANGED
|
@@ -26,7 +26,7 @@ library Layout {
|
|
|
26
26
|
|
|
27
27
|
/// @dev ID encodes an account.
|
|
28
28
|
uint8 constant Account = 0x01;
|
|
29
|
-
/// @dev ID encodes a network node (host, command, or
|
|
29
|
+
/// @dev ID encodes a network node (host, command, or remote).
|
|
30
30
|
uint8 constant Node = 0x02;
|
|
31
31
|
/// @dev ID encodes an asset.
|
|
32
32
|
uint8 constant Asset = 0x03;
|
|
@@ -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
|
|
42
|
+
/// @dev Keccak account — opaque 28-byte keccak commitment.
|
|
43
43
|
uint8 constant Keccak = 0x03;
|
|
44
44
|
|
|
45
45
|
// -------------------------------------------------------------------------
|
|
@@ -50,8 +50,8 @@ library Layout {
|
|
|
50
50
|
uint8 constant Host = 0x01;
|
|
51
51
|
/// @dev Node is a command contract.
|
|
52
52
|
uint8 constant Command = 0x02;
|
|
53
|
-
/// @dev Node is a
|
|
54
|
-
uint8 constant
|
|
53
|
+
/// @dev Node is a remote contract.
|
|
54
|
+
uint8 constant Remote = 0x03;
|
|
55
55
|
/// @dev Node is a query contract.
|
|
56
56
|
uint8 constant Query = 0x04;
|
|
57
57
|
|
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) {
|