@rootzero/contracts 1.29.0 → 1.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +64 -0
- package/Codec.sol +2 -1
- package/Commands.sol +1 -1
- package/Core.sol +1 -1
- package/Endpoints.sol +2 -2
- package/README.md +90 -43
- package/Utils.sol +2 -1
- package/codec/Blocks.sol +41 -67
- package/codec/Decoders.sol +12 -20
- package/codec/Descriptors.sol +3 -14
- package/codec/Keys.sol +1 -3
- package/codec/Schema.sol +1 -3
- package/codec/Specs.sol +2 -4
- package/codec/Writers.sol +8 -9
- package/commands/Base.sol +3 -2
- package/commands/Cashout.sol +18 -16
- package/commands/Relay.sol +14 -15
- package/commands/admin/Execute.sol +2 -2
- package/core/Access.sol +14 -12
- package/core/Calls.sol +90 -102
- package/core/Host.sol +13 -11
- package/core/Pipeline.sol +173 -19
- package/core/Settlement.sol +1 -1
- package/execution/Execution.sol +25 -37
- package/package.json +1 -1
- package/ports/Allowance.sol +7 -17
- package/ports/Base.sol +4 -1
- package/ports/Dispatch.sol +1 -1
- package/ports/Pipe.sol +1 -1
- package/queries/Base.sol +1 -1
- package/utils/Accounts.sol +4 -4
- package/utils/Assets.sol +4 -5
- package/utils/Cursors.sol +24 -1
- package/utils/Flags.sol +18 -0
- package/utils/Layout.sol +3 -3
- package/utils/Nodes.sol +36 -31
- package/utils/Utils.sol +5 -5
package/codec/Keys.sol
CHANGED
|
@@ -11,8 +11,6 @@ library Keys {
|
|
|
11
11
|
bytes4 constant Empty = bytes4(0);
|
|
12
12
|
/// @dev Input amount - (bytes32 asset, uint amount)
|
|
13
13
|
bytes4 constant Amount = bytes4(keccak256("#amount"));
|
|
14
|
-
/// @dev Native-asset cashout request - (uint amount)
|
|
15
|
-
bytes4 constant Cashout = bytes4(keccak256("#cashout"));
|
|
16
14
|
/// @dev Pipeline bootstrap request - (bytes32 asset, uint amount, uint budget)
|
|
17
15
|
bytes4 constant Bootstrap = bytes4(keccak256("#bootstrap"));
|
|
18
16
|
/// @dev Ledger balance - (bytes32 asset, uint amount)
|
|
@@ -41,7 +39,7 @@ library Keys {
|
|
|
41
39
|
bytes4 constant Transaction = bytes4(keccak256("#transaction"));
|
|
42
40
|
/// @dev Sub-command invocation - (uint cmd, uint value, #bytes as input)
|
|
43
41
|
bytes4 constant Step = bytes4(keccak256("#step"));
|
|
44
|
-
/// @dev
|
|
42
|
+
/// @dev Pipeline handoff envelope - (#bytes as input, #bytes as steps)
|
|
45
43
|
bytes4 constant Relay = bytes4(keccak256("#relay"));
|
|
46
44
|
/// @dev Command context transport - (bytes32 account, #bytes as state, #bytes as input)
|
|
47
45
|
bytes4 constant Context = bytes4(keccak256("#context"));
|
package/codec/Schema.sol
CHANGED
|
@@ -89,8 +89,6 @@ library Schemas {
|
|
|
89
89
|
string constant Account = "bytes32 account";
|
|
90
90
|
string constant Asset = "bytes32 asset";
|
|
91
91
|
string constant Status = "uint code";
|
|
92
|
-
string constant Cashout = "uint amount";
|
|
93
|
-
|
|
94
92
|
// Two-word payloads
|
|
95
93
|
|
|
96
94
|
string constant Amount = "bytes32 asset, uint amount";
|
|
@@ -119,7 +117,7 @@ library Schemas {
|
|
|
119
117
|
|
|
120
118
|
string constant Step = "uint cmd, uint value, #bytes as input";
|
|
121
119
|
string constant Call = "uint target, uint resources, #bytes as payload";
|
|
122
|
-
string constant Relay = "
|
|
120
|
+
string constant Relay = "#bytes as input, #bytes as steps";
|
|
123
121
|
string constant Dispatch = "uint portal, uint resources, #bytes as payload";
|
|
124
122
|
string constant Context = "bytes32 account, #bytes as state, #bytes as input";
|
|
125
123
|
string constant Recover = "uint handler, uint resources, bytes32 key, #bytes as witness";
|
package/codec/Specs.sol
CHANGED
|
@@ -25,8 +25,6 @@ library Sizes {
|
|
|
25
25
|
uint constant Step = 2 * Header + 2 * Word;
|
|
26
26
|
/// @dev STATUS block: 8 header + 32 status code = 40 bytes
|
|
27
27
|
uint constant Status = B32;
|
|
28
|
-
/// @dev CASHOUT block: 8 header + 32 native-asset amount = 40 bytes
|
|
29
|
-
uint constant Cashout = B32;
|
|
30
28
|
/// @dev BOOTSTRAP block: 8 header + 32 asset + 32 amount + 32 budget = 104 bytes
|
|
31
29
|
uint constant Bootstrap = B96;
|
|
32
30
|
/// @dev AMOUNT block: 8 header + 32 asset + 32 amount = 72 bytes
|
|
@@ -63,13 +61,13 @@ library Specs {
|
|
|
63
61
|
uint private constant Exact96 = 96 * SizeFields;
|
|
64
62
|
uint private constant Exact128 = 128 * SizeFields;
|
|
65
63
|
uint private constant UnboundedHint128 = uint(128) << 136;
|
|
64
|
+
uint private constant UnboundedMin16Hint256 = (uint(16) << 192) | (uint(256) << 136);
|
|
66
65
|
uint private constant UnboundedMin40Hint256 = (uint(40) << 192) | (uint(256) << 136);
|
|
67
66
|
uint private constant UnboundedMin72Hint256 = (uint(72) << 192) | (uint(256) << 136);
|
|
68
67
|
uint private constant UnboundedMin48Hint512 = (uint(48) << 192) | (uint(512) << 136);
|
|
69
68
|
uint private constant UnboundedMin104Hint256 = (uint(104) << 192) | (uint(256) << 136);
|
|
70
69
|
|
|
71
70
|
uint constant Empty = uint(bytes32(Keys.Empty));
|
|
72
|
-
uint constant Cashout = uint(bytes32(Keys.Cashout)) | Exact32;
|
|
73
71
|
uint constant Bootstrap = uint(bytes32(Keys.Bootstrap)) | Exact96;
|
|
74
72
|
uint constant Amount = uint(bytes32(Keys.Amount)) | Exact64;
|
|
75
73
|
uint constant Balance = uint(bytes32(Keys.Balance)) | Exact64;
|
|
@@ -85,7 +83,7 @@ library Specs {
|
|
|
85
83
|
uint constant Account = uint(bytes32(Keys.Account)) | Exact32;
|
|
86
84
|
uint constant Transaction = uint(bytes32(Keys.Transaction)) | Exact128;
|
|
87
85
|
uint constant Step = uint(bytes32(Keys.Step)) | UnboundedMin72Hint256;
|
|
88
|
-
uint constant Relay = uint(bytes32(Keys.Relay)) |
|
|
86
|
+
uint constant Relay = uint(bytes32(Keys.Relay)) | UnboundedMin16Hint256;
|
|
89
87
|
uint constant Context = uint(bytes32(Keys.Context)) | UnboundedMin48Hint512;
|
|
90
88
|
uint constant Recover = uint(bytes32(Keys.Recover)) | UnboundedMin104Hint256;
|
|
91
89
|
uint constant Dispatch = uint(bytes32(Keys.Dispatch)) | UnboundedMin72Hint256;
|
package/codec/Writers.sol
CHANGED
|
@@ -433,13 +433,12 @@ library Writers {
|
|
|
433
433
|
|
|
434
434
|
/// @notice Append a RELAY block.
|
|
435
435
|
/// @param writer Destination writer.
|
|
436
|
-
/// @param portal Destination portal to encode.
|
|
437
|
-
/// @param resources Packed resources to encode.
|
|
438
436
|
/// @param input Relay input to encode.
|
|
439
|
-
|
|
440
|
-
|
|
437
|
+
/// @param steps Remaining steps to encode.
|
|
438
|
+
function appendRelay(Writer memory writer, bytes memory input, bytes memory steps) internal pure {
|
|
439
|
+
uint size = 3 * Sizes.Header + input.length + steps.length;
|
|
441
440
|
uint i = reserve(writer, size);
|
|
442
|
-
Blocks.writeRelay(writer.dst, i,
|
|
441
|
+
Blocks.writeRelay(writer.dst, i, input, steps);
|
|
443
442
|
}
|
|
444
443
|
|
|
445
444
|
/// @notice Append a DISPATCH block.
|
|
@@ -574,11 +573,11 @@ library Writers {
|
|
|
574
573
|
Blocks.copyCall(writer.dst, i, target, resources, payload);
|
|
575
574
|
}
|
|
576
575
|
|
|
577
|
-
/// @notice Append a RELAY block by copying its nested
|
|
578
|
-
function copyRelay(Writer memory writer,
|
|
579
|
-
uint size =
|
|
576
|
+
/// @notice Append a RELAY block by copying its nested streams from calldata.
|
|
577
|
+
function copyRelay(Writer memory writer, bytes calldata input, bytes calldata steps) internal pure {
|
|
578
|
+
uint size = 3 * Sizes.Header + input.length + steps.length;
|
|
580
579
|
uint i = reserve(writer, size);
|
|
581
|
-
Blocks.copyRelay(writer.dst, i,
|
|
580
|
+
Blocks.copyRelay(writer.dst, i, input, steps);
|
|
582
581
|
}
|
|
583
582
|
|
|
584
583
|
/// @notice Append a DISPATCH block by copying its nested payload from calldata.
|
package/commands/Base.sol
CHANGED
|
@@ -7,7 +7,8 @@ import {Blocks} from "../codec/Blocks.sol";
|
|
|
7
7
|
import {Specs} from "../codec/Specs.sol";
|
|
8
8
|
import {HostAmount, Position} from "../core/Types.sol";
|
|
9
9
|
import {Execution, Executions} from "../execution/Execution.sol";
|
|
10
|
-
import {Descriptors
|
|
10
|
+
import {Descriptors} from "../codec/Descriptors.sol";
|
|
11
|
+
import {Flags} from "../utils/Flags.sol";
|
|
11
12
|
import {Nodes} from "../utils/Nodes.sol";
|
|
12
13
|
|
|
13
14
|
using Executions for Execution;
|
|
@@ -62,7 +63,7 @@ abstract contract CommandBase is CallerAccess, EndpointBase {
|
|
|
62
63
|
/// @return id Command node ID.
|
|
63
64
|
/// @return published Published endpoint descriptor.
|
|
64
65
|
function command(string memory name, uint descriptor) internal returns (uint id, uint published) {
|
|
65
|
-
id = Nodes.toCommand(name, address(this));
|
|
66
|
+
id = Nodes.toCommand(name, address(this), uint8(descriptor));
|
|
66
67
|
published = endpoint(id, name, descriptor);
|
|
67
68
|
}
|
|
68
69
|
|
package/commands/Cashout.sol
CHANGED
|
@@ -2,19 +2,18 @@
|
|
|
2
2
|
pragma solidity ^0.8.33;
|
|
3
3
|
|
|
4
4
|
import {Execution, Executions, CommandBase, Specs} from "./Base.sol";
|
|
5
|
-
import {
|
|
5
|
+
import {Memory} from "../codec/Blocks.sol";
|
|
6
6
|
import {Sizes} from "../codec/Specs.sol";
|
|
7
|
-
import {Cursors} from "../utils/Cursors.sol";
|
|
8
7
|
import {Action} from "../annotations/Action.sol";
|
|
9
8
|
import {Actions} from "../utils/Actions.sol";
|
|
10
|
-
import {
|
|
9
|
+
import {InvalidAsset, UnexpectedInput} from "../utils/Errors.sol";
|
|
11
10
|
|
|
12
11
|
using Executions for Execution;
|
|
13
12
|
|
|
14
13
|
/// @notice Hook implemented by hosts that withdraw native assets from accounts.
|
|
15
14
|
abstract contract CashoutHook {
|
|
16
15
|
/// @notice Withdraw an exact native-asset amount from `account`.
|
|
17
|
-
/// Called once per
|
|
16
|
+
/// Called once per native BALANCE block in state.
|
|
18
17
|
/// @dev Implementations must revert when the requested amount cannot be withdrawn.
|
|
19
18
|
/// @param account Account whose native asset is withdrawn.
|
|
20
19
|
/// @param amount Native-asset amount to withdraw.
|
|
@@ -28,7 +27,7 @@ abstract contract Cashout is CommandBase, CashoutHook, Action {
|
|
|
28
27
|
uint private immutable id;
|
|
29
28
|
|
|
30
29
|
constructor() {
|
|
31
|
-
(id, descriptor) = command("cashout", Specs.
|
|
30
|
+
(id, descriptor) = command("cashout", Specs.Balance, Specs.Empty, Specs.Empty, 0);
|
|
32
31
|
action(id, Actions.Cashout);
|
|
33
32
|
}
|
|
34
33
|
|
|
@@ -37,8 +36,8 @@ abstract contract Cashout is CommandBase, CashoutHook, Action {
|
|
|
37
36
|
return id;
|
|
38
37
|
}
|
|
39
38
|
|
|
40
|
-
/// @notice Withdraw native
|
|
41
|
-
/// @param context Command context carrying a
|
|
39
|
+
/// @notice Withdraw native BALANCE state from the command account.
|
|
40
|
+
/// @param context Command context carrying a BALANCE state stream.
|
|
42
41
|
/// @return output Empty output state.
|
|
43
42
|
/// @return credit Zero native budget credit.
|
|
44
43
|
function cashout(
|
|
@@ -47,7 +46,9 @@ abstract contract Cashout is CommandBase, CashoutHook, Action {
|
|
|
47
46
|
Execution memory exec = openCommand(context, descriptor);
|
|
48
47
|
|
|
49
48
|
while (exec.more()) {
|
|
50
|
-
|
|
49
|
+
(bytes32 asset, uint amount) = exec.unpackBalance();
|
|
50
|
+
if (asset != nativeAsset) revert InvalidAsset();
|
|
51
|
+
cashout(exec.account, amount);
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
return exec.close();
|
|
@@ -57,10 +58,10 @@ abstract contract Cashout is CommandBase, CashoutHook, Action {
|
|
|
57
58
|
/// @title ExecuteCashout
|
|
58
59
|
/// @notice Extends cashout with optimized local pipeline execution.
|
|
59
60
|
abstract contract ExecuteCashout is Cashout {
|
|
60
|
-
/// @notice Execute cashout directly against
|
|
61
|
+
/// @notice Execute cashout directly against native BALANCE state held in memory.
|
|
61
62
|
/// @param account Account whose native asset is withdrawn.
|
|
62
|
-
/// @param state
|
|
63
|
-
/// @param input
|
|
63
|
+
/// @param state BALANCE block stream held in pipeline memory.
|
|
64
|
+
/// @param input Empty input required by the command schema.
|
|
64
65
|
/// @param value Native value assigned to this command; must be zero.
|
|
65
66
|
/// @return handled Always true because this helper executed the command.
|
|
66
67
|
/// @return output Empty output state.
|
|
@@ -72,14 +73,15 @@ abstract contract ExecuteCashout is Cashout {
|
|
|
72
73
|
uint value
|
|
73
74
|
) internal returns (bool handled, bytes memory output, uint credit) {
|
|
74
75
|
if (value != 0) revert ValueNotAllowed();
|
|
75
|
-
if (
|
|
76
|
-
if (input.length % Sizes.Cashout != 0) revert Blocks.InvalidBlock();
|
|
76
|
+
if (input.length != 0) revert UnexpectedInput();
|
|
77
77
|
|
|
78
|
-
(uint abs, uint end) =
|
|
78
|
+
(uint abs, uint end) = Memory.bounds(state, Sizes.Balance);
|
|
79
79
|
while (abs < end) {
|
|
80
|
-
|
|
80
|
+
(bytes32 asset, uint amount) = Memory.unpackBalance(abs);
|
|
81
|
+
if (asset != nativeAsset) revert InvalidAsset();
|
|
82
|
+
cashout(account, amount);
|
|
81
83
|
unchecked {
|
|
82
|
-
abs += Sizes.
|
|
84
|
+
abs += Sizes.Balance;
|
|
83
85
|
}
|
|
84
86
|
}
|
|
85
87
|
return (true, "", 0);
|
package/commands/Relay.sol
CHANGED
|
@@ -7,26 +7,25 @@ using Executions for Execution;
|
|
|
7
7
|
|
|
8
8
|
/// @notice Hook implemented by hosts that relay command contexts.
|
|
9
9
|
abstract contract RelayPayableHook {
|
|
10
|
-
/// @notice Override to relay a command context
|
|
10
|
+
/// @notice Override to relay a command context and its pipeline continuation.
|
|
11
11
|
/// @dev Relay hooks may forward only EMPTY or BALANCE state. They must not
|
|
12
12
|
/// relay DEBT, POSITION, or other state whose destination handling can fail:
|
|
13
13
|
/// the source state is consumed before destination success is known.
|
|
14
|
-
///
|
|
15
|
-
///
|
|
16
|
-
///
|
|
17
|
-
/// plain native value. EVM adapters extract the low 128-bit value lane with
|
|
18
|
-
/// `useResourceValue`; higher bits may encode execution gas or other data.
|
|
14
|
+
/// `funds` contains only this handoff STEP's assigned value, not the source
|
|
15
|
+
/// pipeline's complete remaining budget. Implementations must consume or
|
|
16
|
+
/// forward `state`; the command returns empty state when the hook completes.
|
|
19
17
|
/// @param account Destination command account.
|
|
20
18
|
/// @param state Complete state forwarded into the destination context.
|
|
21
|
-
/// @param input
|
|
19
|
+
/// @param input Command-specific input supplied by the handoff step.
|
|
20
|
+
/// Implementations define and decode this stream according to their transport.
|
|
21
|
+
/// @param steps Remaining STEP stream owned by the handoff.
|
|
22
22
|
/// @param funds Execution carrying value available for transport fees and
|
|
23
23
|
/// destination resource funding.
|
|
24
24
|
function relay(
|
|
25
|
-
uint portal,
|
|
26
|
-
uint resources,
|
|
27
25
|
bytes32 account,
|
|
28
26
|
bytes calldata state,
|
|
29
27
|
bytes calldata input,
|
|
28
|
+
bytes calldata steps,
|
|
30
29
|
Execution memory funds
|
|
31
30
|
) internal virtual;
|
|
32
31
|
}
|
|
@@ -37,14 +36,14 @@ abstract contract RelayPayable is CommandBase, RelayPayableHook {
|
|
|
37
36
|
uint private immutable descriptor;
|
|
38
37
|
|
|
39
38
|
constructor() {
|
|
40
|
-
(, descriptor) = command("relayPayable", Specs.Empty, Specs.Relay, Specs.Empty, Flags.
|
|
39
|
+
(, descriptor) = command("relayPayable", Specs.Empty, Specs.Relay, Specs.Empty, Flags.HandoffFunded);
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
/// @notice Relay one RELAY input block with the command account and empty state.
|
|
44
43
|
function relayPayable(bytes calldata context) external payable onlyCommand returns (bytes memory, uint) {
|
|
45
44
|
Execution memory exec = openCommand(context, descriptor);
|
|
46
|
-
(
|
|
47
|
-
relay(
|
|
45
|
+
(bytes calldata input, bytes calldata steps) = exec.oninput().unpackRelay();
|
|
46
|
+
relay(exec.account, context[0:0], input, steps, exec);
|
|
48
47
|
return exec.close();
|
|
49
48
|
}
|
|
50
49
|
}
|
|
@@ -58,7 +57,7 @@ abstract contract RelayBalancePayable is CommandBase, RelayPayableHook {
|
|
|
58
57
|
uint private immutable descriptor;
|
|
59
58
|
|
|
60
59
|
constructor() {
|
|
61
|
-
(, descriptor) = command("relayBalancePayable", Specs.Balance, Specs.Relay, Specs.Empty, Flags.
|
|
60
|
+
(, descriptor) = command("relayBalancePayable", Specs.Balance, Specs.Relay, Specs.Empty, Flags.HandoffFunded);
|
|
62
61
|
}
|
|
63
62
|
|
|
64
63
|
/// @notice Relay one RELAY input block with the command account and current state.
|
|
@@ -67,8 +66,8 @@ abstract contract RelayBalancePayable is CommandBase, RelayPayableHook {
|
|
|
67
66
|
/// @return Native value to add to the caller's budget.
|
|
68
67
|
function relayBalancePayable(bytes calldata context) external payable onlyCommand returns (bytes memory, uint) {
|
|
69
68
|
Execution memory exec = openCommand(context, descriptor);
|
|
70
|
-
(
|
|
71
|
-
relay(
|
|
69
|
+
(bytes calldata input, bytes calldata steps) = exec.oninput().unpackRelay();
|
|
70
|
+
relay(exec.account, exec.takeRawState(), input, steps, exec);
|
|
72
71
|
return exec.close();
|
|
73
72
|
}
|
|
74
73
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
pragma solidity ^0.8.33;
|
|
3
3
|
|
|
4
4
|
import {AdminBase, Execution, Executions, Flags, Specs} from "./Base.sol";
|
|
5
|
-
import {
|
|
5
|
+
import {rawCall} from "../../core/Calls.sol";
|
|
6
6
|
|
|
7
7
|
using Executions for Execution;
|
|
8
8
|
|
|
@@ -13,7 +13,7 @@ using Executions for Execution;
|
|
|
13
13
|
/// through `useResourceValue`.
|
|
14
14
|
/// Only callable by the admin account.
|
|
15
15
|
/// Unspent top-level `msg.value` is returned as native budget credit.
|
|
16
|
-
abstract contract ExecutePayable is
|
|
16
|
+
abstract contract ExecutePayable is AdminBase {
|
|
17
17
|
uint private immutable descriptor;
|
|
18
18
|
|
|
19
19
|
constructor() {
|
package/core/Access.sol
CHANGED
|
@@ -6,7 +6,6 @@ import {GuardianEvent} from "../events/Guardian.sol";
|
|
|
6
6
|
import {Runtime} from "./Runtime.sol";
|
|
7
7
|
import {Accounts} from "../utils/Accounts.sol";
|
|
8
8
|
import {Nodes} from "../utils/Nodes.sol";
|
|
9
|
-
import {addrOr} from "../utils/Utils.sol";
|
|
10
9
|
|
|
11
10
|
/// @dev Thrown when a caller, account, or node lacks required access.
|
|
12
11
|
error AccessDenied();
|
|
@@ -37,20 +36,23 @@ abstract contract TrustAccess {
|
|
|
37
36
|
/// @title CommanderAccess
|
|
38
37
|
/// @notice Minimal commander-based access control shared by host access policies.
|
|
39
38
|
abstract contract CommanderAccess is Runtime {
|
|
40
|
-
/// @dev
|
|
41
|
-
|
|
42
|
-
address internal immutable commander;
|
|
39
|
+
/// @dev Commander host ID. Defaults to this contract's host ID when self-managed.
|
|
40
|
+
uint internal immutable commander;
|
|
43
41
|
|
|
44
|
-
/// @
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
/// @dev Native commander address resolved from `commander` at construction.
|
|
43
|
+
address internal immutable commanderAddr;
|
|
44
|
+
|
|
45
|
+
/// @param cmdr Local host ID of the commander, or zero to make this contract self-managed.
|
|
46
|
+
constructor(uint cmdr) {
|
|
47
|
+
commander = cmdr == 0 ? host : cmdr;
|
|
48
|
+
commanderAddr = cmdr == 0 ? address(this) : Nodes.hostAddr(cmdr);
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
/// @notice Assert that `caller` is the commander and return it.
|
|
50
52
|
/// @param caller Address to validate.
|
|
51
53
|
/// @return The same `caller` value if it is the commander.
|
|
52
54
|
function enforceCommander(address caller) internal view returns (address) {
|
|
53
|
-
if (caller == address(0) || caller !=
|
|
55
|
+
if (caller == address(0) || caller != commanderAddr) {
|
|
54
56
|
revert AccessDenied();
|
|
55
57
|
}
|
|
56
58
|
return caller;
|
|
@@ -61,11 +63,11 @@ abstract contract CommanderAccess is Runtime {
|
|
|
61
63
|
/// @title AdminAccess
|
|
62
64
|
/// @notice Commander access with the admin account derived from the commander.
|
|
63
65
|
abstract contract AdminAccess is CommanderAccess {
|
|
64
|
-
/// @dev Admin account ID derived from the commander address at construction time.
|
|
66
|
+
/// @dev Admin account ID derived from the resolved commander address at construction time.
|
|
65
67
|
bytes32 internal immutable admin;
|
|
66
68
|
|
|
67
69
|
constructor() {
|
|
68
|
-
admin = Accounts.toAdmin(
|
|
70
|
+
admin = Accounts.toAdmin(commanderAddr);
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
/// @notice Assert that `account` is the admin account and `caller` is the commander.
|
|
@@ -99,7 +101,7 @@ abstract contract NodeAccess is AdminAccess, TrustAccess, NodeEvent {
|
|
|
99
101
|
/// whose host ID has been explicitly authorized.
|
|
100
102
|
/// @param caller Address to check.
|
|
101
103
|
function isTrustedCaller(address caller) internal view returns (bool) {
|
|
102
|
-
return caller ==
|
|
104
|
+
return caller == commanderAddr || caller == address(this) || nodes[Nodes.toHost(caller)];
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
/// @notice Assert that `node` is in the trusted set and return it.
|
|
@@ -112,7 +114,7 @@ abstract contract NodeAccess is AdminAccess, TrustAccess, NodeEvent {
|
|
|
112
114
|
|
|
113
115
|
/// @notice Assert that `caller` is a trusted peer other than the commander.
|
|
114
116
|
function enforcePeer(address caller) internal view returns (address) {
|
|
115
|
-
if (caller ==
|
|
117
|
+
if (caller == commanderAddr) revert CommanderNotAllowed();
|
|
116
118
|
if (caller == address(0) || !isTrustedCaller(caller)) {
|
|
117
119
|
revert AccessDenied();
|
|
118
120
|
}
|
package/core/Calls.sol
CHANGED
|
@@ -10,122 +10,110 @@ import {Nodes} from "../utils/Nodes.sol";
|
|
|
10
10
|
/// @param err Revert data returned by the failed call.
|
|
11
11
|
error FailedCall(address addr, bytes4 selector, bytes err);
|
|
12
12
|
|
|
13
|
-
/// @notice
|
|
14
|
-
/// @
|
|
15
|
-
///
|
|
16
|
-
///
|
|
17
|
-
|
|
13
|
+
/// @notice Try a raw low-level call to another node and return whether it succeeded.
|
|
14
|
+
/// @param node Node ID of the callee.
|
|
15
|
+
/// @param value Native value to forward in wei.
|
|
16
|
+
/// @param data Complete encoded calldata to send.
|
|
17
|
+
/// @return success True if the low-level call succeeded.
|
|
18
|
+
function tryRawCall(uint node, uint value, bytes memory data) returns (bool success) {
|
|
19
|
+
address addr = Nodes.addr(node);
|
|
20
|
+
(success, ) = payable(addr).call{value: value}(data);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// @notice Try a raw low-level call using a separate selector and ABI-encoded arguments.
|
|
24
|
+
/// @dev `args` must not include the selector. The byte-array length is temporarily
|
|
25
|
+
/// used as scratch space so the call does not allocate or copy `selector || args`.
|
|
26
|
+
function tryRawCall(
|
|
18
27
|
bytes4 selector,
|
|
19
|
-
address
|
|
28
|
+
address addr,
|
|
20
29
|
uint value,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
bytes calldata input
|
|
24
|
-
) returns (bytes memory output, uint credit) {
|
|
30
|
+
bytes memory args
|
|
31
|
+
) returns (bool success) {
|
|
25
32
|
assembly ("memory-safe") {
|
|
26
|
-
let
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
// ABI envelope for command(bytes).
|
|
31
|
-
mstore(scratch, selector)
|
|
32
|
-
mstore(add(scratch, 0x04), 0x20)
|
|
33
|
-
mstore(add(scratch, 0x24), ctxlen)
|
|
34
|
-
|
|
35
|
-
// CONTEXT(account, BYTES(state), BYTES(input)).
|
|
36
|
-
let ctx := add(scratch, 0x44)
|
|
37
|
-
// 0xc5769e23 = bytes4(keccak256("#context"))
|
|
38
|
-
mstore(ctx, or(shl(224, 0xc5769e23), shl(192, sub(ctxlen, 8))))
|
|
39
|
-
// `ctxlen` is dead after the header and can carry the complete call length.
|
|
40
|
-
ctxlen := add(68, and(add(ctxlen, 0x1f), not(0x1f)))
|
|
41
|
-
mstore(add(ctx, 0x08), account)
|
|
42
|
-
let stateblk := add(ctx, 0x28)
|
|
43
|
-
// 0x6911b332 = bytes4(keccak256("#bytes"))
|
|
44
|
-
mstore(stateblk, or(shl(224, 0x6911b332), shl(192, statelen)))
|
|
45
|
-
mcopy(add(stateblk, 0x08), add(state, 0x20), statelen)
|
|
46
|
-
let inputblk := add(add(stateblk, 0x08), statelen)
|
|
47
|
-
let inputlen := input.length
|
|
48
|
-
mstore(inputblk, or(shl(224, 0x6911b332), shl(192, inputlen)))
|
|
49
|
-
calldatacopy(add(inputblk, 0x08), input.offset, inputlen)
|
|
50
|
-
|
|
51
|
-
// ABI word alignment also covers the input header's full-word write. Advance
|
|
52
|
-
// the free memory pointer once, after the return-data size is also known.
|
|
53
|
-
let inputend := and(add(add(scratch, ctxlen), 0x1f), not(0x1f))
|
|
54
|
-
let success := call(gas(), target, value, scratch, ctxlen, 0, 0)
|
|
55
|
-
let retlen := returndatasize()
|
|
56
|
-
if iszero(success) {
|
|
57
|
-
// FailedCall(target, selector, returndata)
|
|
58
|
-
// 0x20577b07 = FailedCall(address,bytes4,bytes)
|
|
59
|
-
mstore(scratch, shl(224, 0x20577b07))
|
|
60
|
-
mstore(add(scratch, 0x04), target)
|
|
61
|
-
mstore(add(scratch, 0x24), selector)
|
|
62
|
-
mstore(add(scratch, 0x44), 0x60)
|
|
63
|
-
mstore(add(scratch, 0x64), retlen)
|
|
64
|
-
mstore(add(add(scratch, 0x84), retlen), 0)
|
|
65
|
-
returndatacopy(add(scratch, 0x84), 0, retlen)
|
|
66
|
-
revert(scratch, add(0x84, and(add(retlen, 0x1f), not(0x1f))))
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if lt(retlen, 0x60) { revert(0, 0) }
|
|
70
|
-
returndatacopy(scratch, 0, retlen)
|
|
71
|
-
|
|
72
|
-
// Strictly validate the exact ABI layout for (bytes, uint). The returned
|
|
73
|
-
// byte array can then point directly into the copied returndata.
|
|
74
|
-
if iszero(eq(mload(scratch), 0x40)) { revert(0, 0) }
|
|
75
|
-
let len1 := mload(add(scratch, 0x40))
|
|
76
|
-
if gt(len1, sub(retlen, 0x60)) { revert(0, 0) }
|
|
77
|
-
let pad1 := and(add(len1, 0x1f), not(0x1f))
|
|
78
|
-
if iszero(eq(retlen, add(0x60, pad1))) { revert(0, 0) }
|
|
79
|
-
output := add(scratch, 0x40)
|
|
80
|
-
credit := mload(add(scratch, 0x20))
|
|
81
|
-
|
|
82
|
-
let retend := and(add(add(scratch, retlen), 0x1f), not(0x1f))
|
|
83
|
-
if gt(retend, inputend) { inputend := retend }
|
|
84
|
-
mstore(0x40, inputend)
|
|
33
|
+
let len := mload(args)
|
|
34
|
+
mstore(args, or(and(len, not(0xffffffff)), shr(224, selector)))
|
|
35
|
+
success := call(gas(), addr, value, add(args, 0x1c), add(len, 0x04), 0, 0)
|
|
36
|
+
mstore(args, len)
|
|
85
37
|
}
|
|
86
38
|
}
|
|
87
39
|
|
|
88
|
-
/// @
|
|
89
|
-
/// @
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
40
|
+
/// @notice Make a raw low-level call to another node and revert when it fails.
|
|
41
|
+
/// @param node Node ID of the callee.
|
|
42
|
+
/// @param value Native value to forward in wei.
|
|
43
|
+
/// @param data Complete encoded calldata to send.
|
|
44
|
+
/// @return out Return data from the successful call.
|
|
45
|
+
function rawCall(uint node, uint value, bytes memory data) returns (bytes memory out) {
|
|
46
|
+
bool success;
|
|
47
|
+
address addr = Nodes.addr(node);
|
|
48
|
+
(success, out) = payable(addr).call{value: value}(data);
|
|
49
|
+
if (!success) revert FailedCall(addr, bytes4(data), out);
|
|
50
|
+
}
|
|
100
51
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
52
|
+
/// @notice Make a raw low-level call using a separate selector and ABI-encoded arguments.
|
|
53
|
+
/// @dev `args` must not include the selector. The byte-array length is temporarily
|
|
54
|
+
/// used as scratch space so the call does not allocate or copy `selector || args`.
|
|
55
|
+
function rawCall(
|
|
56
|
+
bytes4 selector,
|
|
57
|
+
address addr,
|
|
58
|
+
uint value,
|
|
59
|
+
bytes memory args
|
|
60
|
+
) returns (bytes memory out) {
|
|
61
|
+
bool success;
|
|
62
|
+
assembly ("memory-safe") {
|
|
63
|
+
let len := mload(args)
|
|
64
|
+
mstore(args, or(and(len, not(0xffffffff)), shr(224, selector)))
|
|
65
|
+
success := call(gas(), addr, value, add(args, 0x1c), add(len, 0x04), 0, 0)
|
|
66
|
+
mstore(args, len)
|
|
67
|
+
|
|
68
|
+
let size := returndatasize()
|
|
69
|
+
out := mload(0x40)
|
|
70
|
+
mstore(out, size)
|
|
71
|
+
returndatacopy(add(out, 0x20), 0, size)
|
|
72
|
+
mstore(add(add(out, 0x20), size), 0)
|
|
73
|
+
mstore(0x40, and(add(add(add(out, 0x20), size), 0x1f), not(0x1f)))
|
|
111
74
|
}
|
|
75
|
+
if (!success) revert FailedCall(addr, selector, out);
|
|
76
|
+
}
|
|
112
77
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
78
|
+
/// @notice Make a raw low-level read-only query to another node and revert when it fails.
|
|
79
|
+
/// @param node Node ID of the callee.
|
|
80
|
+
/// @param data Complete encoded calldata to send.
|
|
81
|
+
/// @return out Return data from the successful query.
|
|
82
|
+
function rawQuery(uint node, bytes memory data) view returns (bytes memory out) {
|
|
83
|
+
bool success;
|
|
84
|
+
address addr = Nodes.addr(node);
|
|
85
|
+
(success, out) = addr.staticcall(data);
|
|
86
|
+
if (!success) revert FailedCall(addr, bytes4(data), out);
|
|
87
|
+
}
|
|
123
88
|
|
|
89
|
+
/// @notice Make a raw read-only query using a separate selector and ABI-encoded arguments.
|
|
90
|
+
/// @dev `args` must not include the selector. The byte-array length is temporarily
|
|
91
|
+
/// used as scratch space so the call does not allocate or copy `selector || args`.
|
|
92
|
+
function rawQuery(
|
|
93
|
+
bytes4 selector,
|
|
94
|
+
address addr,
|
|
95
|
+
bytes memory args
|
|
96
|
+
) view returns (bytes memory out) {
|
|
97
|
+
bool success;
|
|
98
|
+
assembly ("memory-safe") {
|
|
99
|
+
let len := mload(args)
|
|
100
|
+
mstore(args, or(and(len, not(0xffffffff)), shr(224, selector)))
|
|
101
|
+
success := staticcall(gas(), addr, add(args, 0x1c), add(len, 0x04), 0, 0)
|
|
102
|
+
mstore(args, len)
|
|
103
|
+
|
|
104
|
+
let size := returndatasize()
|
|
105
|
+
out := mload(0x40)
|
|
106
|
+
mstore(out, size)
|
|
107
|
+
returndatacopy(add(out, 0x20), 0, size)
|
|
108
|
+
mstore(add(add(out, 0x20), size), 0)
|
|
109
|
+
mstore(0x40, and(add(add(add(out, 0x20), size), 0x1f), not(0x1f)))
|
|
110
|
+
}
|
|
111
|
+
if (!success) revert FailedCall(addr, selector, out);
|
|
124
112
|
}
|
|
125
113
|
|
|
126
114
|
/// @title NodeCalls
|
|
127
115
|
/// @notice Trusted low-level inter-node calls backed by a host-provided node policy.
|
|
128
|
-
abstract contract NodeCalls is
|
|
116
|
+
abstract contract NodeCalls is TrustAccess {
|
|
129
117
|
/// @notice Try a trusted low-level call to another node and return whether it succeeded.
|
|
130
118
|
/// @param node Node ID of the callee.
|
|
131
119
|
/// @param value Native value to forward in wei.
|
package/core/Host.sol
CHANGED
|
@@ -28,11 +28,13 @@ interface IHostIntroduction {
|
|
|
28
28
|
/// Calls a deployed commander during construction without adding an inbound
|
|
29
29
|
/// introduction endpoint to the inheriting host.
|
|
30
30
|
abstract contract HostIntroduction is Runtime {
|
|
31
|
-
/// @param cmdr
|
|
31
|
+
/// @param cmdr Local host ID to introduce this host to, or zero for no introduction.
|
|
32
32
|
/// @dev Deployment reverts if a contract commander does not accept `introduce(uint,uint)`.
|
|
33
|
-
constructor(
|
|
34
|
-
if (cmdr ==
|
|
35
|
-
|
|
33
|
+
constructor(uint cmdr) {
|
|
34
|
+
if (cmdr == 0 || cmdr == host) return;
|
|
35
|
+
address target = Nodes.hostAddr(cmdr);
|
|
36
|
+
if (target.code.length == 0) return;
|
|
37
|
+
IHostIntroduction(target).introduce(host, block.number);
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
/// @notice Introduce this host to the contract address embedded in a local EVM node ID.
|
|
@@ -53,9 +55,9 @@ abstract contract CommandHost is CommanderAccess, CallerAccess, HostIntroduction
|
|
|
53
55
|
/// @dev Thrown when a commander-only host is deployed without an external commander.
|
|
54
56
|
error InvalidCommander();
|
|
55
57
|
|
|
56
|
-
/// @param cmdr Nonzero
|
|
57
|
-
constructor(
|
|
58
|
-
if (cmdr ==
|
|
58
|
+
/// @param cmdr Nonzero local host ID allowed to invoke hosted commands.
|
|
59
|
+
constructor(uint cmdr) CommanderAccess(cmdr) HostIntroduction(cmdr) {
|
|
60
|
+
if (cmdr == 0) revert InvalidCommander();
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
function enforceCaller(address caller) internal view virtual override returns (address) {
|
|
@@ -82,10 +84,10 @@ abstract contract Guardians is Appoint, Dismiss, Revoke {
|
|
|
82
84
|
/// optionally introduces itself to a commander host at deployment.
|
|
83
85
|
/// Accepts native ETH payments via the `receive` function.
|
|
84
86
|
abstract contract Host is Admins, Guardians, HostIntroduction, IntroductionEvent, IHostIntroduction {
|
|
85
|
-
/// @param cmdr Commander
|
|
86
|
-
/// If
|
|
87
|
-
/// on it during construction.
|
|
88
|
-
constructor(
|
|
87
|
+
/// @param cmdr Commander host ID; used by the composed access capabilities.
|
|
88
|
+
/// If the encoded native target is a deployed contract, the host
|
|
89
|
+
/// calls `introduce` on it during construction.
|
|
90
|
+
constructor(uint cmdr) CommanderAccess(cmdr) HostIntroduction(cmdr) {}
|
|
89
91
|
|
|
90
92
|
/// @notice Assert that `caller` may invoke commands on a peer-aware host.
|
|
91
93
|
function enforceCaller(address caller) internal view virtual override returns (address) {
|