@rootzero/contracts 0.2.0 → 0.4.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 (161) hide show
  1. package/{contracts/Commands.sol → Commands.sol} +7 -2
  2. package/{contracts/Core.sol → Core.sol} +7 -1
  3. package/{contracts/Blocks.sol → Cursors.sol} +8 -1
  4. package/{contracts/Events.sol → Events.sol} +6 -0
  5. package/README.md +59 -59
  6. package/Utils.sol +18 -0
  7. package/blocks/Cursors.sol +800 -0
  8. package/blocks/Keys.sol +51 -0
  9. package/blocks/Mem.sol +188 -0
  10. package/blocks/Schema.sol +158 -0
  11. package/blocks/Writers.sol +304 -0
  12. package/commands/Base.sol +65 -0
  13. package/commands/Borrow.sol +88 -0
  14. package/commands/Burn.sol +45 -0
  15. package/commands/Create.sol +41 -0
  16. package/commands/Credit.sol +49 -0
  17. package/commands/Debit.sol +58 -0
  18. package/commands/Deposit.sol +57 -0
  19. package/commands/Liquidate.sol +98 -0
  20. package/commands/Liquidity.sol +179 -0
  21. package/commands/Mint.sol +53 -0
  22. package/{contracts/commands → commands}/Pipe.sol +28 -12
  23. package/commands/Provision.sol +84 -0
  24. package/commands/Reclaim.sol +54 -0
  25. package/commands/Redeem.sol +98 -0
  26. package/commands/Remove.sol +41 -0
  27. package/commands/Repay.sol +98 -0
  28. package/commands/Settle.sol +41 -0
  29. package/commands/Stake.sol +129 -0
  30. package/commands/Supply.sol +40 -0
  31. package/commands/Swap.sol +89 -0
  32. package/commands/Transfer.sol +55 -0
  33. package/commands/Unstake.sol +57 -0
  34. package/commands/Withdraw.sol +49 -0
  35. package/commands/admin/Allocate.sol +40 -0
  36. package/commands/admin/AllowAssets.sol +42 -0
  37. package/commands/admin/Authorize.sol +38 -0
  38. package/commands/admin/DenyAssets.sol +42 -0
  39. package/commands/admin/Destroy.sol +38 -0
  40. package/commands/admin/Init.sol +38 -0
  41. package/commands/admin/Relocate.sol +39 -0
  42. package/commands/admin/Unauthorize.sol +38 -0
  43. package/core/Access.sol +79 -0
  44. package/core/Balances.sol +40 -0
  45. package/core/Host.sol +44 -0
  46. package/core/Operation.sol +69 -0
  47. package/core/Validator.sol +46 -0
  48. package/docs/GETTING_STARTED.md +286 -0
  49. package/{contracts/events → events}/Access.sol +7 -0
  50. package/events/Asset.sol +21 -0
  51. package/{contracts/events → events}/Balance.sol +10 -0
  52. package/events/Collateral.sol +24 -0
  53. package/events/Command.sol +24 -0
  54. package/events/Debt.sol +25 -0
  55. package/{contracts/events → events}/Deposit.sol +9 -0
  56. package/events/Emitter.sol +14 -0
  57. package/{contracts/events → events}/Governed.sol +7 -0
  58. package/{contracts/events → events}/HostAnnounced.sol +8 -0
  59. package/{contracts/events → events}/Listing.sol +9 -0
  60. package/{contracts/events → events}/Peer.sol +8 -0
  61. package/{contracts/events → events}/Quote.sol +7 -0
  62. package/{contracts/events → events}/RootZero.sol +5 -0
  63. package/{contracts/events → events}/Withdraw.sol +9 -0
  64. package/interfaces/IHostDiscovery.sol +16 -0
  65. package/package.json +17 -33
  66. package/peer/AllowAssets.sol +44 -0
  67. package/peer/Base.sol +25 -0
  68. package/peer/DenyAssets.sol +44 -0
  69. package/peer/Pull.sol +42 -0
  70. package/peer/Push.sol +42 -0
  71. package/utils/Accounts.sol +90 -0
  72. package/utils/Assets.sol +138 -0
  73. package/utils/ECDSA.sol +58 -0
  74. package/utils/Ids.sol +129 -0
  75. package/utils/Layout.sol +66 -0
  76. package/utils/State.sol +22 -0
  77. package/utils/Utils.sol +194 -0
  78. package/utils/Value.sol +32 -0
  79. package/contracts/Utils.sol +0 -12
  80. package/contracts/blocks/Blocks.sol +0 -818
  81. package/contracts/blocks/Keys.sol +0 -24
  82. package/contracts/blocks/Mem.sol +0 -129
  83. package/contracts/blocks/Schema.sol +0 -105
  84. package/contracts/blocks/Writers.sol +0 -209
  85. package/contracts/combinators/AmountToBalance.sol +0 -25
  86. package/contracts/combinators/AmountToCustody.sol +0 -36
  87. package/contracts/combinators/CustodyToBalance.sol +0 -25
  88. package/contracts/combinators/EachRoute.sol +0 -18
  89. package/contracts/combinators/MapBalance.sol +0 -25
  90. package/contracts/combinators/MapCustody.sol +0 -25
  91. package/contracts/combinators/RouteToBalance.sol +0 -27
  92. package/contracts/commands/Base.sol +0 -40
  93. package/contracts/commands/Borrow.sol +0 -89
  94. package/contracts/commands/Burn.sol +0 -33
  95. package/contracts/commands/Create.sol +0 -32
  96. package/contracts/commands/Credit.sol +0 -36
  97. package/contracts/commands/Debit.sol +0 -46
  98. package/contracts/commands/Deposit.sol +0 -45
  99. package/contracts/commands/Liquidate.sol +0 -101
  100. package/contracts/commands/Liquidity.sol +0 -179
  101. package/contracts/commands/Mint.sol +0 -42
  102. package/contracts/commands/Provision.sol +0 -73
  103. package/contracts/commands/Reclaim.sol +0 -48
  104. package/contracts/commands/Redeem.sol +0 -101
  105. package/contracts/commands/Remove.sol +0 -32
  106. package/contracts/commands/Repay.sol +0 -101
  107. package/contracts/commands/Settle.sol +0 -32
  108. package/contracts/commands/Stake.sol +0 -121
  109. package/contracts/commands/Supply.sol +0 -33
  110. package/contracts/commands/Swap.sol +0 -88
  111. package/contracts/commands/Transfer.sol +0 -44
  112. package/contracts/commands/Unstake.sol +0 -49
  113. package/contracts/commands/Withdraw.sol +0 -37
  114. package/contracts/commands/admin/Allocate.sol +0 -32
  115. package/contracts/commands/admin/AllowAssets.sol +0 -34
  116. package/contracts/commands/admin/Authorize.sol +0 -30
  117. package/contracts/commands/admin/DenyAssets.sol +0 -34
  118. package/contracts/commands/admin/Destroy.sol +0 -27
  119. package/contracts/commands/admin/Init.sol +0 -26
  120. package/contracts/commands/admin/Relocate.sol +0 -30
  121. package/contracts/commands/admin/Unauthorize.sol +0 -30
  122. package/contracts/core/Access.sol +0 -50
  123. package/contracts/core/Balances.sol +0 -23
  124. package/contracts/core/Host.sol +0 -25
  125. package/contracts/core/Operation.sol +0 -32
  126. package/contracts/core/Validator.sol +0 -31
  127. package/contracts/events/Asset.sol +0 -14
  128. package/contracts/events/Collateral.sol +0 -15
  129. package/contracts/events/Command.sol +0 -14
  130. package/contracts/events/Debt.sol +0 -15
  131. package/contracts/events/Emitter.sol +0 -7
  132. package/contracts/interfaces/IHostDiscovery.sol +0 -6
  133. package/contracts/peer/AllowAssets.sol +0 -30
  134. package/contracts/peer/Base.sol +0 -17
  135. package/contracts/peer/DenyAssets.sol +0 -30
  136. package/contracts/peer/Pull.sol +0 -30
  137. package/contracts/peer/Push.sol +0 -30
  138. package/contracts/test/TestBlockHelper.sol +0 -261
  139. package/contracts/test/TestBorrowHost.sol +0 -47
  140. package/contracts/test/TestBurnHost.sol +0 -28
  141. package/contracts/test/TestCreateHost.sol +0 -26
  142. package/contracts/test/TestDiscovery.sol +0 -6
  143. package/contracts/test/TestECDSA.sol +0 -16
  144. package/contracts/test/TestHost.sol +0 -199
  145. package/contracts/test/TestLiquidityHost.sol +0 -145
  146. package/contracts/test/TestMintHost.sol +0 -40
  147. package/contracts/test/TestPeerHost.sol +0 -34
  148. package/contracts/test/TestReclaimHost.sol +0 -48
  149. package/contracts/test/TestRejectEther.sol +0 -8
  150. package/contracts/test/TestRemoveHost.sol +0 -26
  151. package/contracts/test/TestSwapHost.sol +0 -44
  152. package/contracts/test/TestUtils.sol +0 -169
  153. package/contracts/test/TestValidator.sol +0 -10
  154. package/contracts/utils/Accounts.sol +0 -40
  155. package/contracts/utils/Assets.sol +0 -76
  156. package/contracts/utils/Channels.sol +0 -11
  157. package/contracts/utils/ECDSA.sol +0 -36
  158. package/contracts/utils/Ids.sol +0 -75
  159. package/contracts/utils/Layout.sol +0 -22
  160. package/contracts/utils/Utils.sol +0 -126
  161. package/contracts/utils/Value.sol +0 -20
@@ -0,0 +1,49 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import { CommandContext, CommandBase, State } from "./Base.sol";
5
+ import { Cursors, Cur, Schemas } from "../Cursors.sol";
6
+ using Cursors for Cur;
7
+
8
+ string constant NAME = "withdraw";
9
+
10
+ /// @title Withdraw
11
+ /// @notice Command that delivers BALANCE state blocks to an external destination.
12
+ /// Use `withdraw` for assets being sent outside the protocol (e.g. ERC-20 transfers, ETH sends).
13
+ /// For internal balance credits, use `creditAccount` instead.
14
+ abstract contract Withdraw is CommandBase {
15
+ uint internal immutable withdrawId = commandId(NAME);
16
+
17
+ constructor() {
18
+ emit Command(host, NAME, Schemas.Recipient, withdrawId, State.Balances, State.Empty);
19
+ }
20
+
21
+ /// @notice Override to send funds to `account`.
22
+ /// Called once per BALANCE block in state.
23
+ /// @param account Destination account identifier (resolved from RECIPIENT block or caller).
24
+ /// @param asset Asset identifier.
25
+ /// @param meta Asset metadata slot.
26
+ /// @param amount Amount to deliver.
27
+ function withdraw(bytes32 account, bytes32 asset, bytes32 meta, uint amount) internal virtual;
28
+
29
+ function withdraw(
30
+ CommandContext calldata c
31
+ ) external payable onlyCommand(withdrawId, c.target) returns (bytes memory) {
32
+ (Cur memory state, , ) = cursor(c.state, 1);
33
+ Cur memory request = cursor(c.request);
34
+ bytes32 to = request.recipientAfter(c.account);
35
+
36
+ while (state.i < state.bound) {
37
+ (bytes32 asset, bytes32 meta, uint amount) = state.unpackBalance();
38
+ withdraw(to, asset, meta, amount);
39
+ }
40
+
41
+ state.complete();
42
+ return "";
43
+ }
44
+ }
45
+
46
+
47
+
48
+
49
+
@@ -0,0 +1,40 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import { CommandBase, CommandContext, State } from "../Base.sol";
5
+ import { Cursors, Cur, Schemas } from "../../Cursors.sol";
6
+ using Cursors for Cur;
7
+
8
+ string constant NAME = "allocate";
9
+
10
+ /// @title Allocate
11
+ /// @notice Admin command that applies cross-host allocation entries via a virtual hook.
12
+ /// Each ALLOCATION block in the request calls `allocate`. Only callable by the admin account.
13
+ abstract contract Allocate is CommandBase {
14
+ uint internal immutable allocateId = commandId(NAME);
15
+
16
+ constructor() {
17
+ emit Command(host, NAME, Schemas.Allocation, allocateId, State.Empty, State.Empty);
18
+ }
19
+
20
+ /// @dev Override to apply a single allocation entry.
21
+ /// Called once per ALLOCATION block in the request.
22
+ function allocate(uint host, bytes32 asset, bytes32 meta, uint amount) internal virtual;
23
+
24
+ function allocate(CommandContext calldata c) external payable onlyAdmin(c.account) onlyCommand(allocateId, c.target) returns (bytes memory) {
25
+ (Cur memory request, , ) = cursor(c.request, 1);
26
+
27
+ while (request.i < request.bound) {
28
+ (uint host, bytes32 asset, bytes32 meta, uint amount) = request.unpackAllocation();
29
+ allocate(host, asset, meta, amount);
30
+ }
31
+
32
+ request.complete();
33
+ return "";
34
+ }
35
+ }
36
+
37
+
38
+
39
+
40
+
@@ -0,0 +1,42 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import { CommandBase, CommandContext, State } from "../Base.sol";
5
+ import { Cursors, Cur, Schemas } from "../../Cursors.sol";
6
+ using Cursors for Cur;
7
+
8
+ string constant NAME = "allowAssets";
9
+
10
+ /// @title AllowAssets
11
+ /// @notice Admin command that permits a list of (asset, meta) pairs via a virtual hook.
12
+ /// Each ASSET block in the request calls `allowAsset`. Only callable by the admin account.
13
+ abstract contract AllowAssets is CommandBase {
14
+ uint internal immutable allowAssetsId = commandId(NAME);
15
+
16
+ constructor() {
17
+ emit Command(host, NAME, Schemas.Asset, allowAssetsId, State.Empty, State.Empty);
18
+ }
19
+
20
+ /// @dev Override to allow a single asset/meta pair.
21
+ /// Called once per ASSET block in the request.
22
+ function allowAsset(bytes32 asset, bytes32 meta) internal virtual returns (bool);
23
+
24
+ function allowAssets(
25
+ CommandContext calldata c
26
+ ) external payable onlyAdmin(c.account) onlyCommand(allowAssetsId, c.target) returns (bytes memory) {
27
+ (Cur memory request, , ) = cursor(c.request, 1);
28
+
29
+ while (request.i < request.bound) {
30
+ (bytes32 asset, bytes32 meta) = request.unpackAsset();
31
+ allowAsset(asset, meta);
32
+ }
33
+
34
+ request.complete();
35
+ return "";
36
+ }
37
+ }
38
+
39
+
40
+
41
+
42
+
@@ -0,0 +1,38 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import { CommandBase, CommandContext, State } from "../Base.sol";
5
+ import { Cursors, Cur, Schemas } from "../../Cursors.sol";
6
+ using Cursors for Cur;
7
+
8
+ string constant NAME = "authorize";
9
+
10
+ /// @title Authorize
11
+ /// @notice Admin command that grants authorization to a list of node IDs.
12
+ /// Each NODE block in the request is authorized on the host.
13
+ /// Only callable by the admin account.
14
+ abstract contract Authorize is CommandBase {
15
+ uint internal immutable authorizeId = commandId(NAME);
16
+
17
+ constructor() {
18
+ emit Command(host, NAME, Schemas.Node, authorizeId, State.Empty, State.Empty);
19
+ }
20
+
21
+ function authorize(
22
+ CommandContext calldata c
23
+ ) external payable onlyAdmin(c.account) onlyCommand(authorizeId, c.target) returns (bytes memory) {
24
+ (Cur memory request, , ) = cursor(c.request, 1);
25
+
26
+ while (request.i < request.bound) {
27
+ uint node = request.unpackNode();
28
+ access(node, true);
29
+ }
30
+
31
+ request.complete();
32
+ return "";
33
+ }
34
+ }
35
+
36
+
37
+
38
+
@@ -0,0 +1,42 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import { CommandBase, CommandContext, State } from "../Base.sol";
5
+ import { Cursors, Cur, Schemas } from "../../Cursors.sol";
6
+ using Cursors for Cur;
7
+
8
+ string constant NAME = "denyAssets";
9
+
10
+ /// @title DenyAssets
11
+ /// @notice Admin command that blocks a list of (asset, meta) pairs via a virtual hook.
12
+ /// Each ASSET block in the request calls `denyAsset`. Only callable by the admin account.
13
+ abstract contract DenyAssets is CommandBase {
14
+ uint internal immutable denyAssetsId = commandId(NAME);
15
+
16
+ constructor() {
17
+ emit Command(host, NAME, Schemas.Asset, denyAssetsId, State.Empty, State.Empty);
18
+ }
19
+
20
+ /// @dev Override to deny a single asset/meta pair.
21
+ /// Called once per ASSET block in the request.
22
+ function denyAsset(bytes32 asset, bytes32 meta) internal virtual returns (bool);
23
+
24
+ function denyAssets(
25
+ CommandContext calldata c
26
+ ) external payable onlyAdmin(c.account) onlyCommand(denyAssetsId, c.target) returns (bytes memory) {
27
+ (Cur memory request, , ) = cursor(c.request, 1);
28
+
29
+ while (request.i < request.bound) {
30
+ (bytes32 asset, bytes32 meta) = request.unpackAsset();
31
+ denyAsset(asset, meta);
32
+ }
33
+
34
+ request.complete();
35
+ return "";
36
+ }
37
+ }
38
+
39
+
40
+
41
+
42
+
@@ -0,0 +1,38 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import { CommandBase, CommandContext, State } from "../Base.sol";
5
+ import { Cursors, Cur } from "../../Cursors.sol";
6
+
7
+ string constant NAME = "destroy";
8
+
9
+ using Cursors for Cur;
10
+
11
+ /// @title Destroy
12
+ /// @notice Admin command that runs host teardown logic via a virtual hook.
13
+ /// The full request is passed to `destroy` as a cursor. Only callable by the admin account.
14
+ abstract contract Destroy is CommandBase {
15
+ uint internal immutable destroyId = commandId(NAME);
16
+
17
+ constructor(string memory input) {
18
+ emit Command(host, NAME, input, destroyId, State.Empty, State.Empty);
19
+ }
20
+
21
+ /// @notice Override to run host teardown or destruction logic.
22
+ /// @param input Cursor over the full request byte stream.
23
+ function destroy(Cur memory input) internal virtual;
24
+
25
+ function destroy(
26
+ CommandContext calldata c
27
+ ) external payable onlyAdmin(c.account) onlyCommand(destroyId, c.target) returns (bytes memory) {
28
+ Cur memory input = cursor(c.request);
29
+ destroy(input);
30
+ return "";
31
+ }
32
+ }
33
+
34
+
35
+
36
+
37
+
38
+
@@ -0,0 +1,38 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import { CommandBase, CommandContext, State } from "../Base.sol";
5
+ import { Cursors, Cur } from "../../Cursors.sol";
6
+
7
+ string constant NAME = "init";
8
+
9
+ using Cursors for Cur;
10
+
11
+ /// @title Init
12
+ /// @notice Admin command that runs host initialization logic via a virtual hook.
13
+ /// The full request is passed to `init` as a cursor. Only callable by the admin account.
14
+ abstract contract Init is CommandBase {
15
+ uint internal immutable initId = commandId(NAME);
16
+
17
+ constructor(string memory input) {
18
+ emit Command(host, NAME, input, initId, State.Empty, State.Empty);
19
+ }
20
+
21
+ /// @notice Override to run host initialization logic.
22
+ /// @param input Cursor over the full request byte stream.
23
+ function init(Cur memory input) internal virtual;
24
+
25
+ function init(
26
+ CommandContext calldata c
27
+ ) external payable onlyAdmin(c.account) onlyCommand(initId, c.target) returns (bytes memory) {
28
+ Cur memory input = cursor(c.request);
29
+ init(input);
30
+ return "";
31
+ }
32
+ }
33
+
34
+
35
+
36
+
37
+
38
+
@@ -0,0 +1,39 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import { CommandBase, CommandContext, State } from "../Base.sol";
5
+ import { Cursors, Cur, Schemas } from "../../Cursors.sol";
6
+ using Cursors for Cur;
7
+
8
+ string constant NAME = "relocate";
9
+
10
+ /// @title Relocate
11
+ /// @notice Admin command that forwards native value (ETH) to one or more destination hosts.
12
+ /// Each FUNDING block in the request specifies a target host node ID and an amount to forward.
13
+ /// Only callable by the admin account.
14
+ abstract contract Relocate is CommandBase {
15
+ uint internal immutable relocateId = commandId(NAME);
16
+
17
+ constructor() {
18
+ emit Command(host, NAME, Schemas.Funding, relocateId, State.Empty, State.Empty);
19
+ }
20
+
21
+ function relocate(
22
+ CommandContext calldata c
23
+ ) external payable onlyAdmin(c.account) onlyCommand(relocateId, c.target) returns (bytes memory) {
24
+ (Cur memory request, , ) = cursor(c.request, 1);
25
+
26
+ while (request.i < request.bound) {
27
+ (uint host, uint amount) = request.unpackFunding();
28
+ callTo(host, amount, "");
29
+ }
30
+
31
+ request.complete();
32
+ return "";
33
+ }
34
+ }
35
+
36
+
37
+
38
+
39
+
@@ -0,0 +1,38 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import { CommandBase, CommandContext, State } from "../Base.sol";
5
+ import { Cursors, Cur, Schemas } from "../../Cursors.sol";
6
+ using Cursors for Cur;
7
+
8
+ string constant NAME = "unauthorize";
9
+
10
+ /// @title Unauthorize
11
+ /// @notice Admin command that revokes authorization from a list of node IDs.
12
+ /// Each NODE block in the request is deauthorized on the host.
13
+ /// Only callable by the admin account.
14
+ abstract contract Unauthorize is CommandBase {
15
+ uint internal immutable unauthorizeId = commandId(NAME);
16
+
17
+ constructor() {
18
+ emit Command(host, NAME, Schemas.Node, unauthorizeId, State.Empty, State.Empty);
19
+ }
20
+
21
+ function unauthorize(
22
+ CommandContext calldata c
23
+ ) external payable onlyAdmin(c.account) onlyCommand(unauthorizeId, c.target) returns (bytes memory) {
24
+ (Cur memory request, , ) = cursor(c.request, 1);
25
+
26
+ while (request.i < request.bound) {
27
+ uint node = request.unpackNode();
28
+ access(node, false);
29
+ }
30
+
31
+ request.complete();
32
+ return "";
33
+ }
34
+ }
35
+
36
+
37
+
38
+
@@ -0,0 +1,79 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import { AccessEvent } from "../events/Access.sol";
5
+ import { Accounts } from "../utils/Accounts.sol";
6
+ import { Ids } from "../utils/Ids.sol";
7
+ import { addrOr } from "../utils/Utils.sol";
8
+
9
+ /// @title AccessControl
10
+ /// @notice Host access control layer.
11
+ /// Tracks an immutable trusted commander, the host's own node ID, and a
12
+ /// mapping of externally authorized node IDs. Inbound trust is host-based:
13
+ /// authorized hosts, the commander, and this contract itself may interact
14
+ /// with the host through the guarded command and peer entrypoints.
15
+ abstract contract AccessControl is AccessEvent {
16
+ /// @dev Trusted commander address. All calls from this address are implicitly trusted.
17
+ /// Defaults to `address(this)` when no external commander is provided.
18
+ address internal immutable commander;
19
+ /// @dev Admin account ID derived from the commander address at construction time.
20
+ bytes32 internal immutable adminAccount;
21
+ /// @dev This host's node ID, set to `Ids.toHost(address(this))` at construction.
22
+ uint public immutable host;
23
+
24
+ /// @dev Mapping from node ID to authorization status.
25
+ /// Authorised nodes may interact with the host as trusted callers or call targets.
26
+ mapping(uint => bool) internal authorized;
27
+
28
+ /// @dev Thrown when `ensureTrusted` is called with a node that is not authorized.
29
+ error UnauthorizedNode(uint node);
30
+ /// @dev Thrown when `enforceCaller` is called by an address that is not trusted.
31
+ error UnauthorizedCaller(address addr);
32
+
33
+ constructor(address cmdr) {
34
+ commander = addrOr(cmdr, address(this));
35
+ adminAccount = Accounts.toAdmin(commander);
36
+ host = Ids.toHost(address(this));
37
+ }
38
+
39
+ /// @notice Grant or revoke authorization for a node.
40
+ /// Inbound authentication is host-based: the node ID used here should be a host ID.
41
+ /// @param node Node ID to authorize or deauthorize.
42
+ /// @param allow True to grant authorization, false to revoke it.
43
+ function access(uint node, bool allow) internal {
44
+ authorized[node] = allow;
45
+ emit Access(host, node, allow);
46
+ }
47
+
48
+ /// @notice Return true if `caller` is an implicitly trusted address.
49
+ /// Trusted callers: the commander, this contract itself, or any address
50
+ /// whose host ID has been explicitly authorized.
51
+ /// @param caller Address to check.
52
+ function isTrusted(address caller) internal view returns (bool) {
53
+ return caller == commander || caller == address(this) || authorized[Ids.toHost(caller)];
54
+ }
55
+
56
+ /// @notice Assert that `caller` is trusted and return it.
57
+ /// Used by command and peer modifiers to gate execution to authorized senders.
58
+ /// @param caller Address to validate.
59
+ /// @return The same `caller` value if trusted.
60
+ function enforceCaller(address caller) internal view returns (address) {
61
+ if (caller == address(0) || !isTrusted(caller)) {
62
+ revert UnauthorizedCaller(caller);
63
+ }
64
+ return caller;
65
+ }
66
+
67
+ /// @notice Assert that `node` is in the authorized set and return it.
68
+ /// Used for outbound trust checks before calling another node.
69
+ /// Accepts any authorized node ID (host or command).
70
+ /// Inbound caller authentication is host-only via `enforceCaller(msg.sender)`.
71
+ /// @param node Node ID to validate.
72
+ /// @return The same `node` value if authorized.
73
+ function ensureTrusted(uint node) internal view returns (uint) {
74
+ if (node == 0 || !authorized[node]) {
75
+ revert UnauthorizedNode(node);
76
+ }
77
+ return node;
78
+ }
79
+ }
@@ -0,0 +1,40 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import {BalanceEvent} from "../events/Balance.sol";
5
+
6
+ /// @dev Thrown when a debit would reduce a balance below zero.
7
+ error InsufficientFunds();
8
+
9
+ /// @title Balances
10
+ /// @notice On-chain ledger for per-account, per-asset token balances.
11
+ /// Balances are keyed by `(account, assetKey)` where `assetKey` is the
12
+ /// value returned by `Assets.key(asset, meta)`.
13
+ abstract contract Balances is BalanceEvent {
14
+ /// @dev account -> assetKey -> balance (in the asset's native units).
15
+ mapping(bytes32 account => mapping(bytes32 assetKey => uint amount)) internal balances;
16
+
17
+ /// @notice Deduct `amount` from an account balance and return the new balance.
18
+ /// Reverts with `InsufficientFunds` if the current balance is less than `amount`.
19
+ /// @param account Account identifier.
20
+ /// @param assetKey Storage key for the (asset, meta) pair.
21
+ /// @param amount Amount to deduct.
22
+ /// @return balance New balance after the debit.
23
+ function debitFrom(bytes32 account, bytes32 assetKey, uint amount) internal returns (uint balance) {
24
+ balance = balances[account][assetKey];
25
+ if (balance < amount) revert InsufficientFunds();
26
+ unchecked {
27
+ balance -= amount;
28
+ }
29
+ balances[account][assetKey] = balance;
30
+ }
31
+
32
+ /// @notice Add `amount` to an account balance and return the new balance.
33
+ /// @param account Account identifier.
34
+ /// @param assetKey Storage key for the (asset, meta) pair.
35
+ /// @param amount Amount to credit.
36
+ /// @return balance New balance after the credit.
37
+ function creditTo(bytes32 account, bytes32 assetKey, uint amount) internal returns (uint balance) {
38
+ balance = balances[account][assetKey] += amount;
39
+ }
40
+ }
package/core/Host.sol ADDED
@@ -0,0 +1,44 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import { AccessControl } from "./Access.sol";
5
+ import { Authorize } from "../commands/admin/Authorize.sol";
6
+ import { Unauthorize } from "../commands/admin/Unauthorize.sol";
7
+ import { Relocate } from "../commands/admin/Relocate.sol";
8
+ import { HostAnnouncedEvent } from "../events/HostAnnounced.sol";
9
+ import { IHostDiscovery } from "../interfaces/IHostDiscovery.sol";
10
+ import { Ids } from "../utils/Ids.sol";
11
+
12
+ /// @notice Mixin that allows a contract to act as a host discovery registry.
13
+ /// Hosts call `announceHost` on a discovery contract to register themselves.
14
+ abstract contract HostDiscovery is HostAnnouncedEvent, IHostDiscovery {
15
+ /// @notice Announce this host to the discovery registry.
16
+ /// Validates that `id` matches `msg.sender` before emitting.
17
+ /// @param id Host node ID (must equal `Ids.toHost(msg.sender)`).
18
+ /// @param blocknum Block number at which the host was deployed.
19
+ /// @param version Protocol version the host implements.
20
+ /// @param namespace Human-readable namespace string for the host.
21
+ function announceHost(uint id, uint blocknum, uint16 version, string calldata namespace) external {
22
+ emit HostAnnounced(Ids.host(id, msg.sender), blocknum, version, namespace);
23
+ }
24
+ }
25
+
26
+ /// @title Host
27
+ /// @notice Abstract base contract for rootzero host implementations.
28
+ /// Inherits admin command support (authorize, unauthorize, relocate) and
29
+ /// optionally announces itself to a discovery contract at deployment.
30
+ /// Accepts native ETH payments via the `receive` function.
31
+ abstract contract Host is Authorize, Unauthorize, Relocate {
32
+ /// @param cmdr Commander address; passed to `AccessControl`.
33
+ /// If `cmdr` is a deployed contract, the host calls `announceHost`
34
+ /// on it during construction to register with the discovery registry.
35
+ /// @param version Protocol version number to publish in the announcement.
36
+ /// @param namespace Human-readable namespace string for the host.
37
+ constructor(address cmdr, uint16 version, string memory namespace) AccessControl(cmdr) {
38
+ if (cmdr == address(0) || cmdr == address(this) || cmdr.code.length == 0) return;
39
+ IHostDiscovery(cmdr).announceHost(host, block.number, version, namespace);
40
+ }
41
+
42
+ /// @notice Accept native ETH transfers (e.g. from command value flows).
43
+ receive() external payable {}
44
+ }
@@ -0,0 +1,69 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import { AccessControl } from "./Access.sol";
5
+ import { Assets } from "../utils/Assets.sol";
6
+ import { Ids } from "../utils/Ids.sol";
7
+ import { Cur, Cursors } from "../Cursors.sol";
8
+
9
+ using Cursors for Cur;
10
+
11
+ /// @dev Emitted when a trusted inter-node call fails.
12
+ /// @param addr Contract address that was called.
13
+ /// @param node Node ID of the callee.
14
+ /// @param selector 4-byte selector of the called function.
15
+ /// @param err Revert data returned by the failed call.
16
+ error FailedCall(address addr, uint node, bytes4 selector, bytes err);
17
+
18
+ /// @title OperationBase
19
+ /// @notice Shared base for command and peer contracts.
20
+ /// Provides convenience wrappers for cursor construction, quotient validation,
21
+ /// and trusted inter-node calls. Inherits access control from `AccessControl`.
22
+ abstract contract OperationBase is AccessControl {
23
+ /// @dev Asset ID for the native chain value (ETH), bound to the current chain at deployment.
24
+ bytes32 public immutable valueAsset = Assets.toValue();
25
+
26
+ /// @notice Open a cursor over a calldata block stream.
27
+ /// @param source Calldata slice to parse.
28
+ /// @return cur Cursor positioned at the beginning of `source`.
29
+ function cursor(bytes calldata source) internal pure returns (Cur memory cur) {
30
+ return Cursors.open(source);
31
+ }
32
+
33
+ /// @notice Open a cursor and prime it for a grouped iteration pass in one call.
34
+ /// Equivalent to `open(source)` followed by `primeRun(group)`.
35
+ /// @param source Calldata slice to parse.
36
+ /// @param group Expected block group size (e.g. 1 for single, 2 for paired).
37
+ /// @return cur Cursor with `bound` set to the end of the first run.
38
+ /// @return count Total number of blocks in the run (a multiple of `group`).
39
+ /// @return quotient Number of groups in the run (`count / group`).
40
+ function cursor(bytes calldata source, uint group) internal pure returns (Cur memory cur, uint count, uint quotient) {
41
+ cur = Cursors.open(source);
42
+ (, count, quotient) = cur.primeRun(group);
43
+ }
44
+
45
+ /// @notice Assert that two normalized group quotients are equal.
46
+ /// Reverts with `Cursors.BadRatio` when `lq != rq`.
47
+ /// @param lq Left-hand quotient.
48
+ /// @param rq Right-hand quotient.
49
+ function checkQuotient(uint lq, uint rq) internal pure {
50
+ if (lq != rq) revert Cursors.BadRatio();
51
+ }
52
+
53
+ /// @notice Make a trusted call to another node in the network.
54
+ /// Looks up the node's contract address via `ensureTrusted` + `Ids.nodeAddr`,
55
+ /// then issues a low-level call forwarding `value` ETH and `data`.
56
+ /// Reverts with `FailedCall` if the call is unsuccessful.
57
+ /// @param node Node ID of the callee (must be in the authorized set).
58
+ /// @param value Native value to forward in wei.
59
+ /// @param data Encoded calldata to send.
60
+ /// @return out Return data from the successful call.
61
+ function callTo(uint node, uint value, bytes memory data) internal returns (bytes memory out) {
62
+ bool success;
63
+ address addr = Ids.nodeAddr(ensureTrusted(node));
64
+ (success, out) = payable(addr).call{value: value}(data);
65
+ if (!success) {
66
+ revert FailedCall(addr, node, bytes4(data), out);
67
+ }
68
+ }
69
+ }
@@ -0,0 +1,46 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import { ECDSA } from "../utils/ECDSA.sol";
5
+
6
+ /// @title Validator
7
+ /// @notice ECDSA proof verification with nonce-based replay protection.
8
+ /// Proofs use the layout `[bytes20 signer][bytes65 sig]` (85 bytes total).
9
+ /// Each (signer, nonce) pair may only be used once; subsequent uses revert.
10
+ abstract contract Validator {
11
+ using ECDSA for bytes32;
12
+
13
+ /// @dev Thrown when the proof is not exactly 85 bytes.
14
+ error InvalidProof();
15
+ /// @dev Thrown when the recovered signer does not match the claimed signer in the proof.
16
+ error InvalidSigner();
17
+ /// @dev Thrown when the (signer, nonce) pair has already been used.
18
+ error InvalidNonce();
19
+
20
+ /// @dev signer address → nonce key → use count (0 = unused, 1+ = used).
21
+ mapping(address account => mapping(uint192 key => uint64)) internal nonces;
22
+
23
+ /// @dev Recover the signer from an Ethereum signed message hash and signature.
24
+ function recover(bytes32 hash, bytes calldata sig) private pure returns (address) {
25
+ return hash.toEthSignedMessageHash().tryRecoverCalldata(sig);
26
+ }
27
+
28
+ /// @notice Verify an 85-byte proof against a message hash and nonce.
29
+ /// Proof layout: `[bytes20 signer][bytes65 sig]`.
30
+ /// Increments the (signer, nonce) counter to prevent replay.
31
+ /// @param hash Message hash that was signed.
32
+ /// @param nonce 192-bit nonce key (typically derived from a deadline or sequence number).
33
+ /// @param proof 85-byte proof: 20-byte signer address followed by a 65-byte ECDSA signature.
34
+ /// @return Verified signer address.
35
+ function verify(bytes32 hash, uint192 nonce, bytes calldata proof) internal returns (address) {
36
+ if (proof.length != 85) revert InvalidProof();
37
+
38
+ address account = address(bytes20(proof[0:20]));
39
+ address signer = recover(hash, proof[20:]);
40
+
41
+ if (account == address(0) || signer != account) revert InvalidSigner();
42
+ if (nonces[account][nonce]++ != 0) revert InvalidNonce();
43
+
44
+ return account;
45
+ }
46
+ }