@rootzero/contracts 1.26.0 → 1.28.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.
@@ -7,6 +7,7 @@ import {Action} from "../annotations/Action.sol";
7
7
  import {Actions} from "../utils/Actions.sol";
8
8
  import {Blocks, Memory} from "../codec/Blocks.sol";
9
9
  import {Sizes} from "../codec/Specs.sol";
10
+ import {UnexpectedInput} from "../utils/Errors.sol";
10
11
 
11
12
  using Executions for Execution;
12
13
 
@@ -108,10 +109,10 @@ abstract contract SettleInternal is Settle {
108
109
  bytes32 account,
109
110
  bytes memory state,
110
111
  bytes calldata input,
111
- uint128 value
112
+ uint value
112
113
  ) internal returns (bytes memory, uint) {
113
114
  if (value != 0) revert ValueNotAllowed();
114
- if (input.length != 0) revert Executions.ZeroStride();
115
+ if (input.length != 0) revert UnexpectedInput();
115
116
  if (state.length == 0) revert Blocks.EmptyRun();
116
117
 
117
118
  (uint abs, uint end) = Memory.bounds(state, Sizes.Position);
@@ -8,7 +8,9 @@ using Executions for Execution;
8
8
 
9
9
  /// @title ExecutePayable
10
10
  /// @notice Admin command that forwards raw calldata to one or more target nodes.
11
- /// Each CALL block specifies a target node ID, packed resources, and raw calldata payload.
11
+ /// Each CALL block specifies a target node ID, opaque packed resources, and raw
12
+ /// calldata payload. Packed resources are converted to plain native value only
13
+ /// through `useResourceValue`.
12
14
  /// Only callable by the admin account.
13
15
  /// Unspent top-level `msg.value` is returned as native budget credit.
14
16
  abstract contract ExecutePayable is RawNodeCalls, AdminBase {
package/core/Access.sol CHANGED
@@ -89,6 +89,7 @@ abstract contract NodeAccess is AdminAccess, TrustAccess, NodeEvent {
89
89
  /// @param node Node ID to update.
90
90
  /// @param active True to authorize the node, false to revoke it.
91
91
  function setNode(uint node, bool active) internal {
92
+ node = Nodes.local(node);
92
93
  nodes[node] = active;
93
94
  emit Node(host, node, active);
94
95
  }
@@ -105,9 +106,7 @@ abstract contract NodeAccess is AdminAccess, TrustAccess, NodeEvent {
105
106
  /// @param node Node ID to validate.
106
107
  /// @return The same `node` value if trusted.
107
108
  function ensureTrusted(uint node) internal view override returns (uint) {
108
- if (node == 0 || !nodes[node]) {
109
- revert AccessDenied();
110
- }
109
+ if (!nodes[node]) revert AccessDenied();
111
110
  return node;
112
111
  }
113
112
 
package/core/Calls.sol CHANGED
@@ -3,9 +3,6 @@ pragma solidity ^0.8.33;
3
3
 
4
4
  import {TrustAccess} from "./Access.sol";
5
5
  import {Nodes} from "../utils/Nodes.sol";
6
- import {Keys} from "../codec/Keys.sol";
7
- import {Sizes} from "../codec/Specs.sol";
8
- import {max32} from "../utils/Utils.sol";
9
6
 
10
7
  /// @dev Emitted when a trusted inter-node call fails.
11
8
  /// @param addr Contract address that was called.
@@ -13,6 +10,81 @@ import {max32} from "../utils/Utils.sol";
13
10
  /// @param err Revert data returned by the failed call.
14
11
  error FailedCall(address addr, bytes4 selector, bytes err);
15
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(
18
+ bytes4 selector,
19
+ address target,
20
+ uint value,
21
+ bytes32 account,
22
+ bytes memory state,
23
+ bytes calldata input
24
+ ) returns (bytes memory output, uint credit) {
25
+ 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)
85
+ }
86
+ }
87
+
16
88
  /// @title RawNodeCalls
17
89
  /// @notice Low-level inter-node call helpers without target authorization.
18
90
  abstract contract RawNodeCalls {
@@ -21,7 +93,7 @@ abstract contract RawNodeCalls {
21
93
  /// @param value Native value to forward in wei.
22
94
  /// @param data Encoded calldata to send.
23
95
  /// @return success True if the low-level call succeeded.
24
- function tryRawCall(uint node, uint128 value, bytes memory data) internal returns (bool success) {
96
+ function tryRawCall(uint node, uint value, bytes memory data) internal returns (bool success) {
25
97
  address addr = Nodes.addr(node);
26
98
  (success, ) = payable(addr).call{value: value}(data);
27
99
  }
@@ -31,7 +103,7 @@ abstract contract RawNodeCalls {
31
103
  /// @param value Native value to forward in wei.
32
104
  /// @param data Encoded calldata to send.
33
105
  /// @return out Return data from the successful call.
34
- function rawCall(uint node, uint128 value, bytes memory data) internal returns (bytes memory out) {
106
+ function rawCall(uint node, uint value, bytes memory data) internal returns (bytes memory out) {
35
107
  bool success;
36
108
  address addr = Nodes.addr(node);
37
109
  (success, out) = payable(addr).call{value: value}(data);
@@ -59,7 +131,7 @@ abstract contract NodeCalls is RawNodeCalls, TrustAccess {
59
131
  /// @param value Native value to forward in wei.
60
132
  /// @param data Encoded calldata to send.
61
133
  /// @return success True if the low-level call succeeded.
62
- function tryTrustedCall(uint node, uint128 value, bytes memory data) internal returns (bool success) {
134
+ function tryTrustedCall(uint node, uint value, bytes memory data) internal returns (bool success) {
63
135
  return tryRawCall(ensureTrusted(node), value, data);
64
136
  }
65
137
 
@@ -68,7 +140,7 @@ abstract contract NodeCalls is RawNodeCalls, TrustAccess {
68
140
  /// @param value Native value to forward in wei.
69
141
  /// @param data Encoded calldata to send.
70
142
  /// @return out Return data from the successful call.
71
- function trustedCall(uint node, uint128 value, bytes memory data) internal returns (bytes memory out) {
143
+ function trustedCall(uint node, uint value, bytes memory data) internal returns (bytes memory out) {
72
144
  return rawCall(ensureTrusted(node), value, data);
73
145
  }
74
146
 
@@ -81,74 +153,6 @@ abstract contract NodeCalls is RawNodeCalls, TrustAccess {
81
153
  }
82
154
  }
83
155
 
84
- /// @title CommandCalls
85
- /// @notice Trusted command-call helpers for contracts that route command nodes.
86
- abstract contract CommandCalls is NodeCalls {
87
- /// @dev Build `command(bytes)` calldata and its nested CONTEXT block in one allocation.
88
- /// Threaded state is copied from memory and step input directly from calldata.
89
- function encodeCommandCall(
90
- bytes4 selector,
91
- bytes32 account,
92
- bytes memory state,
93
- bytes calldata input
94
- ) internal pure returns (bytes memory data) {
95
- uint contextLen = max32(Sizes.B32 + 2 * Sizes.Header + state.length + input.length);
96
- uint paddedContextLen = (contextLen + 31) & ~uint(31);
97
- uint dataLen = 4 + 64 + paddedContextLen;
98
-
99
- // Reserve one scratch word because the final eight-byte block header is
100
- // written with mstore. Exclude that word from the returned calldata.
101
- data = new bytes(dataLen + 32);
102
-
103
- uint contextKey = uint32(Keys.Context);
104
- uint bytesKey = uint32(Keys.Bytes);
105
- assembly ("memory-safe") {
106
- mstore(data, dataLen)
107
- let out := add(data, 0x20)
108
-
109
- // ABI envelope for command(bytes).
110
- mstore(out, selector)
111
- mstore(add(out, 0x04), 0x20)
112
- mstore(add(out, 0x24), contextLen)
113
-
114
- // CONTEXT(account, BYTES(state), BYTES(input)).
115
- let context := add(out, 0x44)
116
- mstore(context, or(shl(224, contextKey), shl(192, sub(contextLen, 8))))
117
- mstore(add(context, 0x08), account)
118
-
119
- let stateBlock := add(context, 0x28)
120
- let stateLen := mload(state)
121
- mstore(stateBlock, or(shl(224, bytesKey), shl(192, stateLen)))
122
- mcopy(add(stateBlock, 0x08), add(state, 0x20), stateLen)
123
-
124
- let inputBlock := add(add(stateBlock, 0x08), stateLen)
125
- let inputLen := input.length
126
- mstore(inputBlock, or(shl(224, bytesKey), shl(192, inputLen)))
127
- calldatacopy(add(inputBlock, 0x08), input.offset, inputLen)
128
- }
129
- }
130
-
131
- /// @notice Encode and call a trusted command node.
132
- /// @param command Command node ID embedding the target selector.
133
- /// @param value Native value to forward in wei.
134
- /// @param account Command account identifier.
135
- /// @param state Current command state block stream.
136
- /// @param input Command input block stream.
137
- /// @return nextState Decoded command output state block stream.
138
- /// @return credit Trusted native value to add to the caller's execution budget.
139
- function callCommand(
140
- uint command,
141
- uint128 value,
142
- bytes32 account,
143
- bytes memory state,
144
- bytes calldata input
145
- ) internal returns (bytes memory nextState, uint credit) {
146
- bytes4 selector = Nodes.commandSelector(command);
147
- bytes memory data = encodeCommandCall(selector, account, state, input);
148
- return abi.decode(trustedCall(command, value, data), (bytes, uint));
149
- }
150
- }
151
-
152
156
  /// @title PortCalls
153
157
  /// @notice Trusted port-call helpers for contracts that route port nodes.
154
158
  abstract contract PortCalls is NodeCalls {
@@ -157,7 +161,7 @@ abstract contract PortCalls is NodeCalls {
157
161
  /// @param value Native value to forward in wei.
158
162
  /// @param input Port input block stream.
159
163
  /// @return success True if the low-level port call succeeded.
160
- function tryCallPort(uint port, uint128 value, bytes memory input) internal returns (bool success) {
164
+ function tryCallPort(uint port, uint value, bytes memory input) internal returns (bool success) {
161
165
  bytes4 selector = Nodes.portSelector(port);
162
166
  bytes memory data = abi.encodeWithSelector(selector, input);
163
167
  return tryTrustedCall(port, value, data);
@@ -168,7 +172,7 @@ abstract contract PortCalls is NodeCalls {
168
172
  /// @param value Native value to forward in wei.
169
173
  /// @param input Port input block stream.
170
174
  /// @return success True if the low-level port call succeeded.
171
- function tryCallPortCopy(uint port, uint128 value, bytes calldata input) internal returns (bool success) {
175
+ function tryCallPortCopy(uint port, uint value, bytes calldata input) internal returns (bool success) {
172
176
  bytes4 selector = Nodes.portSelector(port);
173
177
  bytes memory data = abi.encodeWithSelector(selector, input);
174
178
  return tryTrustedCall(port, value, data);
@@ -179,7 +183,7 @@ abstract contract PortCalls is NodeCalls {
179
183
  /// @param value Native value to forward in wei.
180
184
  /// @param input Port input block stream.
181
185
  /// @return Decoded port output block stream.
182
- function callPort(uint port, uint128 value, bytes memory input) internal returns (bytes memory) {
186
+ function callPort(uint port, uint value, bytes memory input) internal returns (bytes memory) {
183
187
  bytes4 selector = Nodes.portSelector(port);
184
188
  bytes memory data = abi.encodeWithSelector(selector, input);
185
189
  return abi.decode(trustedCall(port, value, data), (bytes));
@@ -190,7 +194,7 @@ abstract contract PortCalls is NodeCalls {
190
194
  /// @param value Native value to forward in wei.
191
195
  /// @param input Port input block stream.
192
196
  /// @return Decoded port output block stream.
193
- function callPortCopy(uint port, uint128 value, bytes calldata input) internal returns (bytes memory) {
197
+ function callPortCopy(uint port, uint value, bytes calldata input) internal returns (bytes memory) {
194
198
  bytes4 selector = Nodes.portSelector(port);
195
199
  bytes memory data = abi.encodeWithSelector(selector, input);
196
200
  return abi.decode(trustedCall(port, value, data), (bytes));
package/core/Pipeline.sol CHANGED
@@ -2,8 +2,11 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import {Blocks} from "../codec/Blocks.sol";
5
+ import {TrustAccess} from "./Access.sol";
6
+ import {rawCommandCall} from "./Calls.sol";
5
7
  import {Cursors} from "../utils/Cursors.sol";
6
- import {InsufficientValue, OutOfBounds} from "../utils/Errors.sol";
8
+ import {InsufficientValue, OutOfBounds, UnexpectedState} from "../utils/Errors.sol";
9
+ import {unpackCommand} from "../utils/Nodes.sol";
7
10
 
8
11
  /// @notice Hook implemented by hosts that execute encoded step streams.
9
12
  abstract contract PipeHook {
@@ -16,30 +19,34 @@ abstract contract PipeHook {
16
19
  ) internal virtual returns (uint remaining);
17
20
  }
18
21
 
22
+ /// @notice Hook implemented by pipeline hosts that execute host-local commands.
23
+ abstract contract ExecuteHook {
24
+ /// @notice Execute one command whose node ID targets the current host.
25
+ /// @dev Implementations must revert for unsupported local command IDs.
26
+ function execute(
27
+ uint cmd,
28
+ bytes32 account,
29
+ bytes memory state,
30
+ bytes calldata input,
31
+ uint value
32
+ ) internal virtual returns (bytes memory output, uint credit);
33
+ }
34
+
19
35
  /// @title Pipeline
20
36
  /// @notice Core pipeline functionality shared by higher-level surfaces.
21
- abstract contract Pipeline is PipeHook {
22
- /// @dev Thrown when the pipeline finishes with non-empty threaded state.
23
- error UnexpectedState();
24
-
25
- /// @notice Override to dispatch one piped step.
26
- /// Called once per STEP block. The returned state becomes the state passed to
27
- /// the next step, and the final returned state must be empty. Returned
28
- /// credit is added to the shared native-value budget before the next step runs.
29
- /// @param cmd Command node ID to invoke or handle.
30
- /// @param account Account identifier for the piped context.
31
- /// @param state Current threaded state block stream.
32
- /// @param input Step input block stream.
33
- /// @param value Native EVM value assigned to this step.
34
- /// @return output Updated state block stream for the next step.
35
- /// @return credit Trusted native value to add to the pipeline budget.
36
- function dispatch(
37
+ abstract contract Pipeline is TrustAccess, PipeHook, ExecuteHook {
38
+ function run(
37
39
  uint cmd,
38
40
  bytes32 account,
39
41
  bytes memory state,
40
42
  bytes calldata input,
41
- uint128 value
42
- ) internal virtual returns (bytes memory output, uint credit);
43
+ uint value
44
+ ) private returns (bytes memory output, uint credit) {
45
+ (bytes4 selector, address target) = unpackCommand(cmd);
46
+ if (target == address(this)) return execute(cmd, account, state, input, value);
47
+ ensureTrusted(cmd);
48
+ return rawCommandCall(selector, target, value, account, state, input);
49
+ }
43
50
 
44
51
  /// @notice Execute a STEP block stream through the pipeline.
45
52
  /// @dev Reverts with `UnexpectedState` if the final threaded state is non-empty.
@@ -58,18 +65,15 @@ abstract contract Pipeline is PipeHook {
58
65
  (uint abs, uint end) = Cursors.bounds(steps);
59
66
 
60
67
  while (abs < end) {
61
- uint cmd;
62
- uint128 value;
63
- bytes calldata input;
64
- (cmd, value, input, abs) = Blocks.unpackStep(abs);
65
- if (abs > end) revert OutOfBounds();
68
+ (uint cmd, uint value, bytes calldata input, uint next) = Blocks.unpackStep(abs);
69
+ if (next > end) revert OutOfBounds();
66
70
  if (value > budget) revert InsufficientValue();
67
71
  unchecked {
68
72
  budget -= value;
69
73
  }
70
- uint credit;
71
- (state, credit) = dispatch(cmd, account, state, input, value);
72
- budget += credit;
74
+ (state, value) = run(cmd, account, state, input, value);
75
+ budget += value;
76
+ abs = next;
73
77
  }
74
78
 
75
79
  if (state.length != 0) revert UnexpectedState();
package/core/Portal.sol CHANGED
@@ -20,7 +20,7 @@ abstract contract Portal is PortCalls, NodeAccess, UnresolvedEvent, ResolvedEven
20
20
  /// @param message Encoded port input to forward.
21
21
  /// @param value Native EVM value assigned to the forwarding attempt.
22
22
  /// @return miss Message digest recorded for recovery when forwarding fails; zero on success.
23
- function forward(uint port, bytes32 key, bytes calldata message, uint128 value) internal returns (bytes32 miss) {
23
+ function forward(uint port, bytes32 key, bytes calldata message, uint value) internal returns (bytes32 miss) {
24
24
  if (tryCallPortCopy(port, value, message)) return bytes32(0);
25
25
 
26
26
  miss = keccak256(message);
@@ -33,7 +33,7 @@ abstract contract Portal is PortCalls, NodeAccess, UnresolvedEvent, ResolvedEven
33
33
  /// @param key Recovery lookup key.
34
34
  /// @param witness Witness payload used to prove and replay recovery.
35
35
  /// @param value Native EVM value assigned to the resolution attempt.
36
- function resolve(uint port, bytes32 key, bytes calldata witness, uint128 value) internal virtual {
36
+ function resolve(uint port, bytes32 key, bytes calldata witness, uint value) internal virtual {
37
37
  if (unresolved[key] != keccak256(witness)) revert BadWitness();
38
38
 
39
39
  delete unresolved[key];
@@ -61,7 +61,8 @@ abstract contract Settlement is PostHook, SettleHook, RepayHook, DebitAccountHoo
61
61
  }
62
62
 
63
63
  /// @notice Settle one position by crediting its asset and debiting its liability.
64
- /// Skips either operation when its corresponding amount is zero.
64
+ /// Skips either operation when its corresponding amount is zero, including
65
+ /// position sides encoded as absent with a zero identifier and quantity.
65
66
  function settle(
66
67
  bytes32 account,
67
68
  bytes32 asset,
package/core/Types.sol CHANGED
@@ -76,6 +76,7 @@ struct Debt {
76
76
  }
77
77
 
78
78
  /// @notice Asset and liability pair threaded as live pipeline state.
79
+ /// Either side may be absent by setting both its identifier and quantity to zero.
79
80
  struct Position {
80
81
  /// @dev Identifier for the asset side.
81
82
  bytes32 asset;
@@ -38,15 +38,19 @@ library Budgets {
38
38
  }
39
39
 
40
40
  /// @notice Deduct the EVM value lane of `resources` from `budget`.
41
- /// @dev EVM resources use the low 128 bits as native value/endowment.
41
+ /// @dev `resources` is not a native value. This helper explicitly extracts
42
+ /// its low 128-bit EVM value lane and widens that lane to a plain `uint`.
42
43
  /// @param budget Mutable budget to debit.
43
44
  /// @param resources Packed resources whose low 128 bits contain native value.
44
45
  /// @return value Native value to forward in wei.
45
- function useResourceValue(Budget memory budget, uint resources) internal pure returns (uint128) {
46
- return uint128(useValue(budget, uint128(resources)));
46
+ function useResourceValue(Budget memory budget, uint resources) internal pure returns (uint value) {
47
+ value = uint128(resources);
48
+ useValue(budget, value);
47
49
  }
48
50
 
49
51
  /// @notice Deduct the EVM value lane of `resources` from a scalar budget.
52
+ /// @dev `resources` is not a native value. This helper explicitly extracts
53
+ /// its low 128-bit EVM value lane and widens that lane to a plain `uint`.
50
54
  /// @param budget Remaining native value in wei.
51
55
  /// @param resources Packed resources whose low 128 bits contain native value.
52
56
  /// @return remaining Native value remaining after the deduction.
@@ -54,7 +58,7 @@ library Budgets {
54
58
  function useResourceValue(
55
59
  uint budget,
56
60
  uint resources
57
- ) internal pure returns (uint remaining, uint128 value) {
61
+ ) internal pure returns (uint remaining, uint value) {
58
62
  value = uint128(resources);
59
63
  remaining = useValue(budget, value);
60
64
  }
@@ -28,9 +28,6 @@ struct Execution {
28
28
  library Executions {
29
29
  using Cursors for uint;
30
30
 
31
- /// @dev Command-defined lane policy rejected data in an expected-empty lane.
32
- error ZeroStride();
33
-
34
31
  // -------------------------------------------------------------------------
35
32
  // Opening
36
33
  // -------------------------------------------------------------------------
@@ -621,7 +618,7 @@ library Executions {
621
618
  /// @return cmd Decoded command identifier.
622
619
  /// @return value Decoded native value.
623
620
  /// @return input Decoded nested input.
624
- function unpackStep(Execution memory exec) internal pure returns (uint cmd, uint128 value, bytes calldata input) {
621
+ function unpackStep(Execution memory exec) internal pure returns (uint cmd, uint value, bytes calldata input) {
625
622
  uint cur = exec.decoders;
626
623
  uint end;
627
624
  (cmd, value, input, end) = Blocks.unpackStep(cur.absolute());
@@ -1032,7 +1029,7 @@ library Executions {
1032
1029
  /// @param cmd Command identifier to encode.
1033
1030
  /// @param value Native value to encode.
1034
1031
  /// @param input Command input to encode.
1035
- function outputStep(Execution memory exec, uint cmd, uint128 value, bytes memory input) internal pure {
1032
+ function outputStep(Execution memory exec, uint cmd, uint value, bytes memory input) internal pure {
1036
1033
  uint size = Sizes.Step + input.length;
1037
1034
  uint i = reserve(exec, size);
1038
1035
  Blocks.writeStep(exec.output, i, cmd, value, input);
@@ -1167,7 +1164,7 @@ library Executions {
1167
1164
  }
1168
1165
 
1169
1166
  /// @notice Append a STEP block to execution output by copying its nested input from calldata.
1170
- function outputCopyStep(Execution memory exec, uint cmd, uint128 value, bytes calldata input) internal pure {
1167
+ function outputCopyStep(Execution memory exec, uint cmd, uint value, bytes calldata input) internal pure {
1171
1168
  uint size = Sizes.Step + input.length;
1172
1169
  uint i = reserve(exec, size);
1173
1170
  Blocks.copyStep(exec.output, i, cmd, value, input);
@@ -1250,12 +1247,14 @@ library Executions {
1250
1247
  }
1251
1248
 
1252
1249
  /// @notice Deduct the EVM value lane of `resources` from the execution budget.
1253
- /// @dev EVM resources use the low 128 bits as native value/endowment.
1250
+ /// @dev `resources` is not a native value. This helper explicitly extracts
1251
+ /// its low 128-bit EVM value lane and widens that lane to a plain `uint`.
1254
1252
  /// @param exec Mutable execution whose budget is charged.
1255
1253
  /// @param resources Packed resources whose value lane should be spent.
1256
1254
  /// @return value Native value to forward in wei.
1257
- function useResourceValue(Execution memory exec, uint resources) internal pure returns (uint128) {
1258
- return uint128(useValue(exec, uint128(resources)));
1255
+ function useResourceValue(Execution memory exec, uint resources) internal pure returns (uint value) {
1256
+ value = uint128(resources);
1257
+ useValue(exec, value);
1259
1258
  }
1260
1259
 
1261
1260
  // -------------------------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rootzero/contracts",
3
- "version": "1.26.0",
3
+ "version": "1.28.0",
4
4
  "description": "Solidity contracts and protocol building blocks for rootzero hosts and commands.",
5
5
  "private": false,
6
6
  "license": "GPL-3.0-only",
@@ -13,8 +13,9 @@ abstract contract DispatchPayableHook {
13
13
  /// @notice Override to dispatch an encoded payload to `portal`.
14
14
  /// @param portal Destination portal implementation's host ID. Implementations
15
15
  /// may validate or resolve it for their transport.
16
- /// @param resources Chain-specific destination resources. EVM adapters
17
- /// may interpret this as packed execution gas and destination value.
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.
18
19
  /// @param payload Encoded payload ready for the transport layer.
19
20
  /// @param funds Execution used for source value available for transport fees
20
21
  /// and destination resource funding.
@@ -34,6 +34,7 @@ library Accounts {
34
34
  }
35
35
 
36
36
  /// @notice Return true if `account` belongs to the EVM account family.
37
+ /// @dev Classification only; does not require a nonzero embedded address.
37
38
  function isEvm(bytes32 account) internal pure returns (bool) {
38
39
  return isFamily(uint(account), Family);
39
40
  }
@@ -44,20 +45,24 @@ library Accounts {
44
45
  }
45
46
 
46
47
  /// @notice Return true if `account` is an admin account.
48
+ /// @dev Classification only; does not require a nonzero embedded address.
47
49
  function isAdmin(bytes32 account) internal pure returns (bool) {
48
50
  return prefix(account) == Admin;
49
51
  }
50
52
 
51
53
  /// @notice Return true if `account` is a user account.
54
+ /// @dev Classification only; does not require a nonzero embedded address.
52
55
  function isUser(bytes32 account) internal pure returns (bool) {
53
56
  return prefix(account) == User;
54
57
  }
55
58
 
56
- /// @notice Assert that `value` belongs to the EVM account family and return it unchanged.
59
+ /// @notice Assert that `value` belongs to the EVM account family, contains
60
+ /// a nonzero embedded address, and return it unchanged.
57
61
  /// @param value Account identifier to validate.
58
62
  /// @return account The same `value` if it is an EVM account.
59
63
  function evm(bytes32 value) internal pure returns (bytes32 account) {
60
- if (!isEvm(value)) revert InvalidAccount();
64
+ if (!isFamily(uint(value), Family)) revert InvalidAccount();
65
+ ensureAddr(address(uint160(uint(value) >> 32)));
61
66
  return value;
62
67
  }
63
68
 
@@ -69,19 +74,23 @@ library Accounts {
69
74
  return value;
70
75
  }
71
76
 
72
- /// @notice Assert that `value` is an admin account and return it unchanged.
77
+ /// @notice Assert that `value` is an admin account, contains a nonzero
78
+ /// embedded address, and return it unchanged.
73
79
  /// @param value Account identifier to validate.
74
80
  /// @return account The same `value` if it is an admin account.
75
81
  function admin(bytes32 value) internal pure returns (bytes32 account) {
76
- if (!isAdmin(value)) revert InvalidAccount();
82
+ if (uint32(uint(value) >> 224) != Admin) revert InvalidAccount();
83
+ ensureAddr(address(uint160(uint(value) >> 32)));
77
84
  return value;
78
85
  }
79
86
 
80
- /// @notice Assert that `value` is a user account and return it unchanged.
87
+ /// @notice Assert that `value` is a user account, contains a nonzero
88
+ /// embedded address, and return it unchanged.
81
89
  /// @param value Account identifier to validate.
82
90
  /// @return account The same `value` if it is a user account.
83
91
  function user(bytes32 value) internal pure returns (bytes32 account) {
84
- if (!isUser(value)) revert InvalidAccount();
92
+ if (uint32(uint(value) >> 224) != User) revert InvalidAccount();
93
+ ensureAddr(address(uint160(uint(value) >> 32)));
85
94
  return value;
86
95
  }
87
96
 
@@ -120,6 +129,6 @@ library Accounts {
120
129
  /// @param account EVM-family account ID.
121
130
  /// @return Embedded address (bits [191:32] of the ID).
122
131
  function addr(bytes32 account) internal pure returns (address) {
123
- return ensureAddr(address(uint160(uint(evm(account)) >> 32)));
132
+ return address(uint160(uint(evm(account)) >> 32));
124
133
  }
125
134
  }
package/utils/Errors.sol CHANGED
@@ -7,6 +7,12 @@ error InsufficientValue();
7
7
  /// @dev Thrown when a decoder or execution is finalized with unread data.
8
8
  error UnconsumedData();
9
9
 
10
+ /// @dev Thrown when an operation requires empty state but receives state data.
11
+ error UnexpectedState();
12
+
13
+ /// @dev Thrown when an operation requires empty input but receives input data.
14
+ error UnexpectedInput();
15
+
10
16
  /// @dev Thrown when an ID does not match the expected convention or type.
11
17
  error InvalidId();
12
18
 
package/utils/Nodes.sol CHANGED
@@ -6,6 +6,21 @@ import {InvalidId} from "./Errors.sol";
6
6
  import {Ids} from "./Ids.sol";
7
7
  import {ensureAddr, isFamily, matchesBase, toLocalBase} from "./Utils.sol";
8
8
 
9
+ /// @notice Validate and unpack a command node into its ABI selector and target address.
10
+ /// @dev Validates only the command type prefix; chain locality and authorization are caller concerns.
11
+ /// @param cmd Command node ID to unpack.
12
+ /// @return selector ABI selector stored in bits [191:160].
13
+ /// @return target Contract address stored in bits [159:0].
14
+ function unpackCommand(uint cmd) pure returns (bytes4 selector, address target) {
15
+ uint32 command = (uint32(Layout.Evm) << 16) | (uint32(Layout.Node) << 8) | uint32(Layout.Command);
16
+ if (uint32(cmd >> 224) != command) revert InvalidId();
17
+
18
+ assembly ("memory-safe") {
19
+ selector := shl(224, and(shr(160, cmd), 0xffffffff))
20
+ target := and(cmd, 0xffffffffffffffffffffffffffffffffffffffff)
21
+ }
22
+ }
23
+
9
24
  /// @title Nodes
10
25
  /// @notice Encoding and decoding helpers for 256-bit node identifiers.
11
26
  ///