@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/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 Portal relay input - (uint portal, uint resources, #bytes as input)
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 = "uint portal, uint resources, #bytes as input";
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)) | UnboundedMin72Hint256;
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
- function appendRelay(Writer memory writer, uint portal, uint resources, bytes memory input) internal pure {
440
- uint size = Sizes.B64 + Sizes.Header + input.length;
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, portal, resources, input);
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 input from calldata.
578
- function copyRelay(Writer memory writer, uint portal, uint resources, bytes calldata input) internal pure {
579
- uint size = Sizes.B64 + Sizes.Header + input.length;
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, portal, resources, input);
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, Flags} from "../codec/Descriptors.sol";
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
 
@@ -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 {Blocks} from "../codec/Blocks.sol";
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 {UnexpectedState} from "../utils/Errors.sol";
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 CASHOUT input block.
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.Empty, Specs.Cashout, Specs.Empty, 0);
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-asset amounts from the command account.
41
- /// @param context Command context carrying a CASHOUT input stream.
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
- cashout(exec.account, exec.unpackCashout());
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 a calldata CASHOUT stream.
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 Empty pipeline state required by the command schema.
63
- /// @param input CASHOUT block stream.
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 (state.length != 0) revert UnexpectedState();
76
- if (input.length % Sizes.Cashout != 0) revert Blocks.InvalidBlock();
76
+ if (input.length != 0) revert UnexpectedInput();
77
77
 
78
- (uint abs, uint end) = Cursors.bounds(input);
78
+ (uint abs, uint end) = Memory.bounds(state, Sizes.Balance);
79
79
  while (abs < end) {
80
- cashout(account, Blocks.unpackCashout(abs));
80
+ (bytes32 asset, uint amount) = Memory.unpackBalance(abs);
81
+ if (asset != nativeAsset) revert InvalidAsset();
82
+ cashout(account, amount);
81
83
  unchecked {
82
- abs += Sizes.Cashout;
84
+ abs += Sizes.Balance;
83
85
  }
84
86
  }
85
87
  return (true, "", 0);
@@ -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 to `portal`.
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
- /// @param portal Destination portal implementation's host ID. Implementations
15
- /// may validate or resolve it for their transport.
16
- /// @param resources Opaque packed chain-specific destination resources, not
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 Input forwarded into the destination context.
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.Funded);
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
- (uint portal, uint resources, bytes calldata input) = exec.oninput().unpackRelay();
47
- relay(portal, resources, exec.account, context[0:0], input, exec);
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.Funded);
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
- (uint portal, uint resources, bytes calldata input) = exec.oninput().unpackRelay();
71
- relay(portal, resources, exec.account, exec.takeRawState(), input, exec);
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 {RawNodeCalls} from "../../core/Calls.sol";
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 RawNodeCalls, AdminBase {
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 Trusted commander address.
41
- /// Defaults to `address(this)` when no external commander is provided.
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
- /// @param cmdr Commander address, or zero to make this contract self-managed.
45
- constructor(address cmdr) {
46
- commander = addrOr(cmdr, address(this));
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 != commander) {
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(commander);
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 == commander || caller == address(this) || nodes[Nodes.toHost(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 == commander) revert CommanderNotAllowed();
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 Execute a raw `command(bytes)` call and decode its state and credit results.
14
- /// @dev The caller must validate and authorize the command before entering this helper.
15
- /// Uses one scratch region for both call data and return data. Successful return data
16
- /// must use the exact ABI layout of `(bytes, uint)`.
17
- function rawCommandCall(
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 target,
28
+ address addr,
20
29
  uint value,
21
- bytes32 account,
22
- bytes memory state,
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 scratch := mload(0x40)
27
- let statelen := mload(state)
28
- let ctxlen := add(56, add(statelen, input.length))
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
- /// @title RawNodeCalls
89
- /// @notice Low-level inter-node call helpers without target authorization.
90
- abstract contract RawNodeCalls {
91
- /// @notice Try a raw low-level call to another node and return whether it succeeded.
92
- /// @param node Node ID of the callee.
93
- /// @param value Native value to forward in wei.
94
- /// @param data Encoded calldata to send.
95
- /// @return success True if the low-level call succeeded.
96
- function tryRawCall(uint node, uint value, bytes memory data) internal returns (bool success) {
97
- address addr = Nodes.addr(node);
98
- (success, ) = payable(addr).call{value: value}(data);
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
- /// @notice Make a raw low-level call to another node and revert when it fails.
102
- /// @param node Node ID of the callee.
103
- /// @param value Native value to forward in wei.
104
- /// @param data Encoded calldata to send.
105
- /// @return out Return data from the successful call.
106
- function rawCall(uint node, uint value, bytes memory data) internal returns (bytes memory out) {
107
- bool success;
108
- address addr = Nodes.addr(node);
109
- (success, out) = payable(addr).call{value: value}(data);
110
- if (!success) revert FailedCall(addr, bytes4(data), out);
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
- /// @notice Make a raw low-level read-only query to another node and revert when it fails.
114
- /// @param node Node ID of the callee.
115
- /// @param data Encoded calldata to send.
116
- /// @return out Return data from the successful query.
117
- function rawQuery(uint node, bytes memory data) internal view returns (bytes memory out) {
118
- bool success;
119
- address addr = Nodes.addr(node);
120
- (success, out) = addr.staticcall(data);
121
- if (!success) revert FailedCall(addr, bytes4(data), out);
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 RawNodeCalls, TrustAccess {
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 Commander address to introduce this host to when it is a deployed contract.
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(address cmdr) {
34
- if (cmdr == address(0) || cmdr == address(this) || cmdr.code.length == 0) return;
35
- introduceTo(Nodes.toHost(cmdr));
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 address allowed to invoke hosted commands.
57
- constructor(address cmdr) CommanderAccess(cmdr) HostIntroduction(cmdr) {
58
- if (cmdr == address(0)) revert InvalidCommander();
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 address; used by the composed access capabilities.
86
- /// If `cmdr` is a deployed contract, the host calls `introduce`
87
- /// on it during construction.
88
- constructor(address cmdr) CommanderAccess(cmdr) HostIntroduction(cmdr) {}
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) {