@rootzero/contracts 1.21.0 → 1.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/CHANGELOG.md +125 -0
  2. package/Endpoints.sol +2 -3
  3. package/Events.sol +1 -1
  4. package/README.md +25 -19
  5. package/annotations/Action.sol +1 -1
  6. package/annotations/Label.sol +1 -1
  7. package/annotations/Schema.sol +36 -3
  8. package/codec/Blocks.sol +100 -29
  9. package/codec/Decoders.sol +62 -8
  10. package/codec/Readers.sol +39 -0
  11. package/codec/Schema.sol +44 -35
  12. package/codec/Writers.sol +8 -0
  13. package/commands/Allocate.sol +5 -8
  14. package/commands/Base.sol +29 -13
  15. package/commands/Burn.sol +5 -7
  16. package/commands/Credit.sol +5 -7
  17. package/commands/Debit.sol +5 -7
  18. package/commands/Deposit.sol +10 -14
  19. package/commands/Payout.sol +5 -8
  20. package/commands/Provision.sol +10 -14
  21. package/commands/Recover.sol +4 -6
  22. package/commands/Relay.sol +16 -22
  23. package/commands/Repay.sol +10 -14
  24. package/commands/Settle.sol +10 -14
  25. package/commands/Withdraw.sol +5 -7
  26. package/commands/admin/AllowAssets.sol +5 -7
  27. package/commands/admin/Allowance.sol +5 -7
  28. package/commands/admin/Annotate.sol +5 -7
  29. package/commands/admin/Appoint.sol +5 -7
  30. package/commands/admin/Authorize.sol +5 -7
  31. package/commands/admin/Base.sol +12 -3
  32. package/commands/admin/DenyAssets.sol +5 -7
  33. package/commands/admin/Dismiss.sol +5 -7
  34. package/commands/admin/Execute.sol +5 -7
  35. package/commands/admin/Unauthorize.sol +5 -7
  36. package/core/Calls.sol +72 -3
  37. package/core/Host.sol +7 -10
  38. package/core/Portal.sol +2 -2
  39. package/events/Dispatch.sol +1 -1
  40. package/events/Positioned.sol +22 -0
  41. package/events/Relay.sol +1 -1
  42. package/events/Route.sol +1 -1
  43. package/execution/Execution.sol +124 -4
  44. package/package.json +2 -3
  45. package/ports/Allowance.sol +22 -12
  46. package/ports/Assets.sol +99 -0
  47. package/ports/Dispatch.sol +2 -1
  48. package/utils/Cursors.sol +3 -3
  49. package/utils/Nodes.sol +1 -1
  50. package/utils/Utils.sol +31 -31
  51. package/docs/Schema.md +0 -427
  52. package/events/Commander.sol +0 -19
  53. package/ports/AllowAssets.sol +0 -34
  54. package/ports/DenyAssets.sol +0 -34
@@ -16,21 +16,19 @@ abstract contract Annotate is AdminBase {
16
16
  }
17
17
 
18
18
  /// @notice Publish each ANNOTATION block in the admin input.
19
- /// @param input ANNOTATION block stream.
19
+ /// @param context Admin command context carrying the ANNOTATION input stream.
20
20
  /// @return Empty output state.
21
21
  /// @return Empty transaction stream.
22
22
  function annotate(
23
- bytes32 account,
24
- bytes calldata state,
25
- bytes calldata input
26
- ) external onlyAdmin(account) returns (bytes memory, bytes memory) {
27
- Execution memory exec = openCommand(state, input, descriptor, 0);
23
+ bytes calldata context
24
+ ) external returns (bytes memory, bytes memory) {
25
+ Execution memory exec = openAdminCommand(context, descriptor, 0);
28
26
 
29
27
  while (exec.more()) {
30
28
  (uint entity, bytes calldata data) = exec.unpackAnnotation(Lanes.Input);
31
29
  emit Annotation(entity, data);
32
30
  }
33
31
 
34
- return close(exec, account);
32
+ return closeCommand(exec);
35
33
  }
36
34
  }
@@ -17,21 +17,19 @@ abstract contract Appoint is GuardianAccess, AdminBase {
17
17
  }
18
18
 
19
19
  /// @notice Appoint each user ACCOUNT block in the admin input as a guardian.
20
- /// @param input ACCOUNT block stream.
20
+ /// @param context Admin command context carrying the ACCOUNT input stream.
21
21
  /// @return Empty output state.
22
22
  /// @return Empty transaction stream.
23
23
  function appoint(
24
- bytes32 account,
25
- bytes calldata state,
26
- bytes calldata input
27
- ) external onlyAdmin(account) returns (bytes memory, bytes memory) {
28
- Execution memory exec = openCommand(state, input, descriptor, 0);
24
+ bytes calldata context
25
+ ) external returns (bytes memory, bytes memory) {
26
+ Execution memory exec = openAdminCommand(context, descriptor, 0);
29
27
 
30
28
  while (exec.more()) {
31
29
  bytes32 guardian = exec.unpackAccount(Lanes.Input);
32
30
  setGuardian(guardian, true);
33
31
  }
34
32
 
35
- return close(exec, account);
33
+ return closeCommand(exec);
36
34
  }
37
35
  }
@@ -22,21 +22,19 @@ abstract contract Authorize is AdminBase {
22
22
  }
23
23
 
24
24
  /// @notice Authorize each NODE block in the admin input.
25
- /// @param input NODE block stream.
25
+ /// @param context Admin command context carrying the NODE input stream.
26
26
  /// @return Empty output state.
27
27
  /// @return Empty transaction stream.
28
28
  function authorize(
29
- bytes32 account,
30
- bytes calldata state,
31
- bytes calldata input
32
- ) external onlyAdmin(account) returns (bytes memory, bytes memory) {
33
- Execution memory exec = openCommand(state, input, descriptor, 0);
29
+ bytes calldata context
30
+ ) external returns (bytes memory, bytes memory) {
31
+ Execution memory exec = openAdminCommand(context, descriptor, 0);
34
32
 
35
33
  while (exec.more()) {
36
34
  uint node = exec.unpackNode(Lanes.Input);
37
35
  setNode(node, true);
38
36
  }
39
37
 
40
- return close(exec, account);
38
+ return closeCommand(exec);
41
39
  }
42
40
  }
@@ -7,9 +7,18 @@ import {NodeAccess} from "../../core/Access.sol";
7
7
  /// @title AdminBase
8
8
  /// @notice Shared base for admin commands.
9
9
  abstract contract AdminBase is NodeAccess, CommandBase {
10
- /// @dev Restrict execution to the commander using the host's admin account.
11
- modifier onlyAdmin(bytes32 account) {
10
+ /// @notice Decode, authorize, and open one admin command context.
11
+ function openAdminCommand(
12
+ bytes calldata context,
13
+ uint descriptor,
14
+ uint batches
15
+ ) internal view returns (Execution memory exec) {
16
+ bytes32 account;
17
+ bytes calldata state;
18
+ bytes calldata input;
19
+ (account, state, input) = unpackCommandContext(context);
12
20
  enforceAdmin(account, msg.sender);
13
- _;
21
+ exec = Executions.open(state, input, descriptor, batches);
22
+ exec.account = account;
14
23
  }
15
24
  }
@@ -23,21 +23,19 @@ abstract contract DenyAssets is AdminBase, DenyAssetsHook {
23
23
  }
24
24
 
25
25
  /// @notice Deny each ASSET block in the admin input.
26
- /// @param input ASSET block stream.
26
+ /// @param context Admin command context carrying the ASSET input stream.
27
27
  /// @return Empty output state.
28
28
  /// @return Empty transaction stream.
29
29
  function denyAssets(
30
- bytes32 account,
31
- bytes calldata state,
32
- bytes calldata input
33
- ) external onlyAdmin(account) returns (bytes memory, bytes memory) {
34
- Execution memory exec = openCommand(state, input, descriptor, 0);
30
+ bytes calldata context
31
+ ) external returns (bytes memory, bytes memory) {
32
+ Execution memory exec = openAdminCommand(context, descriptor, 0);
35
33
 
36
34
  while (exec.more()) {
37
35
  bytes32 asset = exec.unpackAsset(Lanes.Input);
38
36
  denyAsset(asset);
39
37
  }
40
38
 
41
- return close(exec, account);
39
+ return closeCommand(exec);
42
40
  }
43
41
  }
@@ -17,21 +17,19 @@ abstract contract Dismiss is GuardianAccess, AdminBase {
17
17
  }
18
18
 
19
19
  /// @notice Dismiss each user ACCOUNT block in the admin input from guardian status.
20
- /// @param input ACCOUNT block stream.
20
+ /// @param context Admin command context carrying the ACCOUNT input stream.
21
21
  /// @return Empty output state.
22
22
  /// @return Empty transaction stream.
23
23
  function dismiss(
24
- bytes32 account,
25
- bytes calldata state,
26
- bytes calldata input
27
- ) external onlyAdmin(account) returns (bytes memory, bytes memory) {
28
- Execution memory exec = openCommand(state, input, descriptor, 0);
24
+ bytes calldata context
25
+ ) external returns (bytes memory, bytes memory) {
26
+ Execution memory exec = openAdminCommand(context, descriptor, 0);
29
27
 
30
28
  while (exec.more()) {
31
29
  bytes32 guardian = exec.unpackAccount(Lanes.Input);
32
30
  setGuardian(guardian, false);
33
31
  }
34
32
 
35
- return close(exec, account);
33
+ return closeCommand(exec);
36
34
  }
37
35
  }
@@ -19,21 +19,19 @@ abstract contract ExecutePayable is RawNodeCalls, AdminBase {
19
19
  }
20
20
 
21
21
  /// @notice Execute each CALL block in the admin input.
22
- /// @param input CALL block stream.
22
+ /// @param context Admin command context carrying the CALL input stream.
23
23
  /// @return Empty output state.
24
24
  /// @return Remaining native value as a refund transaction stream.
25
25
  function executePayable(
26
- bytes32 account,
27
- bytes calldata state,
28
- bytes calldata input
29
- ) external payable onlyAdmin(account) returns (bytes memory, bytes memory) {
30
- Execution memory exec = openCommand(state, input, descriptor, 0);
26
+ bytes calldata context
27
+ ) external payable returns (bytes memory, bytes memory) {
28
+ Execution memory exec = openAdminCommand(context, descriptor, 0);
31
29
 
32
30
  while (exec.more()) {
33
31
  (uint target, uint resources, bytes calldata data) = exec.unpackCall(Lanes.Input);
34
32
  rawCall(target, exec.useResourceValue(resources), data);
35
33
  }
36
34
 
37
- return close(exec, account);
35
+ return closeCommand(exec);
38
36
  }
39
37
  }
@@ -22,21 +22,19 @@ abstract contract Unauthorize is AdminBase {
22
22
  }
23
23
 
24
24
  /// @notice Unauthorize each NODE block in the admin input.
25
- /// @param input NODE block stream.
25
+ /// @param context Admin command context carrying the NODE input stream.
26
26
  /// @return Empty output state.
27
27
  /// @return Empty transaction stream.
28
28
  function unauthorize(
29
- bytes32 account,
30
- bytes calldata state,
31
- bytes calldata input
32
- ) external onlyAdmin(account) returns (bytes memory, bytes memory) {
33
- Execution memory exec = openCommand(state, input, descriptor, 0);
29
+ bytes calldata context
30
+ ) external returns (bytes memory, bytes memory) {
31
+ Execution memory exec = openAdminCommand(context, descriptor, 0);
34
32
 
35
33
  while (exec.more()) {
36
34
  uint node = exec.unpackNode(Lanes.Input);
37
35
  setNode(node, false);
38
36
  }
39
37
 
40
- return close(exec, account);
38
+ return closeCommand(exec);
41
39
  }
42
40
  }
package/core/Calls.sol CHANGED
@@ -3,6 +3,9 @@ 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";
6
9
 
7
10
  /// @dev Emitted when a trusted inter-node call fails.
8
11
  /// @param addr Contract address that was called.
@@ -81,6 +84,50 @@ abstract contract NodeCalls is RawNodeCalls, TrustAccess {
81
84
  /// @title CommandCalls
82
85
  /// @notice Trusted command-call helpers for contracts that route command nodes.
83
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
+
84
131
  /// @notice Encode and call a trusted command node.
85
132
  /// @param command Command node ID embedding the target selector.
86
133
  /// @param value Native value to forward in wei.
@@ -97,7 +144,7 @@ abstract contract CommandCalls is NodeCalls {
97
144
  bytes calldata input
98
145
  ) internal returns (bytes memory nextState, bytes memory transactions) {
99
146
  bytes4 selector = Nodes.commandSelector(command);
100
- bytes memory data = abi.encodeWithSelector(selector, account, state, input);
147
+ bytes memory data = encodeCommandCall(selector, account, state, input);
101
148
  return abi.decode(trustedCall(command, value, data), (bytes, bytes));
102
149
  }
103
150
  }
@@ -110,7 +157,18 @@ abstract contract PortCalls is NodeCalls {
110
157
  /// @param value Native value to forward in wei.
111
158
  /// @param input Port input block stream.
112
159
  /// @return success True if the low-level port call succeeded.
113
- function tryCallPort(uint port, uint128 value, bytes calldata input) internal returns (bool success) {
160
+ function tryCallPort(uint port, uint128 value, bytes memory input) internal returns (bool success) {
161
+ bytes4 selector = Nodes.portSelector(port);
162
+ bytes memory data = abi.encodeWithSelector(selector, input);
163
+ return tryTrustedCall(port, value, data);
164
+ }
165
+
166
+ /// @notice Try to encode and call a trusted port node by copying input from calldata.
167
+ /// @param port Port node ID embedding the target selector.
168
+ /// @param value Native value to forward in wei.
169
+ /// @param input Port input block stream.
170
+ /// @return success True if the low-level port call succeeded.
171
+ function tryCallPortCopy(uint port, uint128 value, bytes calldata input) internal returns (bool success) {
114
172
  bytes4 selector = Nodes.portSelector(port);
115
173
  bytes memory data = abi.encodeWithSelector(selector, input);
116
174
  return tryTrustedCall(port, value, data);
@@ -121,7 +179,18 @@ abstract contract PortCalls is NodeCalls {
121
179
  /// @param value Native value to forward in wei.
122
180
  /// @param input Port input block stream.
123
181
  /// @return Decoded port output block stream.
124
- function callPort(uint port, uint128 value, bytes calldata input) internal returns (bytes memory) {
182
+ function callPort(uint port, uint128 value, bytes memory input) internal returns (bytes memory) {
183
+ bytes4 selector = Nodes.portSelector(port);
184
+ bytes memory data = abi.encodeWithSelector(selector, input);
185
+ return abi.decode(trustedCall(port, value, data), (bytes));
186
+ }
187
+
188
+ /// @notice Encode and call a trusted port node by copying input from calldata.
189
+ /// @param port Port node ID embedding the target selector.
190
+ /// @param value Native value to forward in wei.
191
+ /// @param input Port input block stream.
192
+ /// @return Decoded port output block stream.
193
+ function callPortCopy(uint port, uint128 value, bytes calldata input) internal returns (bytes memory) {
125
194
  bytes4 selector = Nodes.portSelector(port);
126
195
  bytes memory data = abi.encodeWithSelector(selector, input);
127
196
  return abi.decode(trustedCall(port, value, data), (bytes));
package/core/Host.sol CHANGED
@@ -61,16 +61,19 @@ abstract contract CommandHost is CommanderAccess, CallerAccess, HostIntroduction
61
61
  function enforceCaller(address caller) internal view virtual override returns (address) {
62
62
  return enforceCommander(caller);
63
63
  }
64
-
65
64
  }
66
65
 
67
66
  /// @title Admins
68
67
  /// @notice Optional bundle of the default host administration commands.
69
- abstract contract Admins is Annotate, ExecutePayable, Authorize, Unauthorize {}
68
+ abstract contract Admins is Annotate, ExecutePayable, Authorize, Unauthorize {
69
+
70
+ }
70
71
 
71
72
  /// @title Guardians
72
73
  /// @notice Optional bundle for guardian management and the default revoke guard.
73
- abstract contract Guardians is Appoint, Dismiss, Revoke {}
74
+ abstract contract Guardians is Appoint, Dismiss, Revoke {
75
+
76
+ }
74
77
 
75
78
  /// @title Host
76
79
  /// @notice Abstract base contract for rootzero host implementations.
@@ -78,13 +81,7 @@ abstract contract Guardians is Appoint, Dismiss, Revoke {}
78
81
  /// guardian management, the default guardian revoke action, and
79
82
  /// optionally introduces itself to a commander host at deployment.
80
83
  /// Accepts native ETH payments via the `receive` function.
81
- abstract contract Host is
82
- Admins,
83
- Guardians,
84
- HostIntroduction,
85
- IntroductionEvent,
86
- IHostIntroduction
87
- {
84
+ abstract contract Host is Admins, Guardians, HostIntroduction, IntroductionEvent, IHostIntroduction {
88
85
  /// @param cmdr Commander address; used by the composed access capabilities.
89
86
  /// If `cmdr` is a deployed contract, the host calls `introduce`
90
87
  /// on it during construction.
package/core/Portal.sol CHANGED
@@ -21,7 +21,7 @@ abstract contract Portal is PortCalls, NodeAccess, UnresolvedEvent, ResolvedEven
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
23
  function forward(uint port, bytes32 key, bytes calldata message, uint128 value) internal returns (bytes32 miss) {
24
- if (tryCallPort(port, value, message)) return bytes32(0);
24
+ if (tryCallPortCopy(port, value, message)) return bytes32(0);
25
25
 
26
26
  miss = keccak256(message);
27
27
  unresolved[key] = miss;
@@ -37,6 +37,6 @@ abstract contract Portal is PortCalls, NodeAccess, UnresolvedEvent, ResolvedEven
37
37
  if (unresolved[key] != keccak256(witness)) revert BadWitness();
38
38
 
39
39
  delete unresolved[key];
40
- callPort(port, value, witness);
40
+ callPortCopy(port, value, witness);
41
41
  }
42
42
  }
@@ -8,7 +8,7 @@ abstract contract DispatchEvent is EventEmitter {
8
8
  string private constant ABI = "event Dispatch(uint indexed host, uint portal, uint resources, bytes32 key, bytes32 digest)";
9
9
 
10
10
  /// @param host Host node ID that owns the dispatch.
11
- /// @param portal Destination portal identifier, often the destination host ID.
11
+ /// @param portal Destination portal implementation's host ID.
12
12
  /// @param resources Chain-specific resources assigned to the dispatch.
13
13
  /// @param key Dispatch correlation or recovery lookup key.
14
14
  /// @param digest Digest of the dispatched payload or canonical envelope.
@@ -0,0 +1,22 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ import {EventEmitter} from "./Emitter.sol";
5
+
6
+ /// @notice Emitted when an action produces an account position.
7
+ abstract contract PositionedEvent is EventEmitter {
8
+ string private constant ABI =
9
+ "event Positioned(bytes32 indexed account, bytes32 asset, uint amount, bytes32 liability, uint debt, uint32 action)";
10
+
11
+ /// @param account Account associated with the position.
12
+ /// @param asset Identifier for the asset side.
13
+ /// @param amount Quantity on the asset side.
14
+ /// @param liability Identifier for the liability side.
15
+ /// @param debt Quantity owed on the liability side.
16
+ /// @param action Primary operation hint from `Actions` that produced the position.
17
+ event Positioned(bytes32 indexed account, bytes32 asset, uint amount, bytes32 liability, uint debt, uint32 action);
18
+
19
+ constructor() {
20
+ emit EventAbi(ABI);
21
+ }
22
+ }
package/events/Relay.sol CHANGED
@@ -8,7 +8,7 @@ abstract contract RelayEvent is EventEmitter {
8
8
  string private constant ABI = "event Relay(bytes32 indexed account, uint portal, uint resources, bytes32 key, bytes32 digest)";
9
9
 
10
10
  /// @param account Account that owns the relayed context.
11
- /// @param portal Destination portal identifier, often the destination host ID.
11
+ /// @param portal Destination portal implementation's host ID.
12
12
  /// @param resources Chain-specific resources assigned to the relay.
13
13
  /// @param key Relay correlation or recovery lookup key.
14
14
  /// @param digest Digest of the relayed payload or canonical envelope.
package/events/Route.sol CHANGED
@@ -8,7 +8,7 @@ abstract contract RouteEvent is EventEmitter {
8
8
  string private constant ABI = "event Route(uint indexed host, uint portal, uint status)";
9
9
 
10
10
  /// @param host Host node ID that owns the route.
11
- /// @param portal Destination portal identifier, often the destination host ID.
11
+ /// @param portal Destination portal implementation's host ID.
12
12
  /// @param status Route status. Zero means inactive; nonzero means active.
13
13
  event Route(uint indexed host, uint portal, uint status);
14
14
 
@@ -15,6 +15,7 @@ import {max16} from "../utils/Utils.sol";
15
15
  /// @dev `decoders` contains tagged input and state cursor lanes. Cursor operations
16
16
  /// select one logical lane by swapping it into the lower 128 bits.
17
17
  struct Execution {
18
+ bytes32 account;
18
19
  uint budget;
19
20
  uint decoders;
20
21
  uint writers;
@@ -187,6 +188,52 @@ library Executions {
187
188
  return exec.decoders.any();
188
189
  }
189
190
 
191
+ /// @notice Return a decoder lane's current absolute calldata position.
192
+ /// @param exec Execution whose decoder is inspected.
193
+ /// @param lane Decoder lane to select.
194
+ /// @return Current absolute calldata position of the selected lane.
195
+ function absolute(Execution memory exec, uint8 lane) internal pure returns (uint) {
196
+ return exec.decoders.select(lane).absolute();
197
+ }
198
+
199
+ /// @dev Return the complete validated calldata source for a decoder lane.
200
+ /// @dev Does not consume or depend on the lane's current position. A lane
201
+ /// absent because its descriptor is EMPTY returns an empty calldata slice.
202
+ /// @param exec Execution whose input or state source is requested.
203
+ /// @param lane Input or state decoder lane.
204
+ /// @return data Complete calldata region represented by the lane.
205
+ function raw(Execution memory exec, uint8 lane) private pure returns (bytes calldata data) {
206
+ if (!exec.decoders.contains(lane)) return msg.data[0:0];
207
+
208
+ uint cur = exec.decoders.select(lane);
209
+ (, uint offset, uint len) = cur.decode();
210
+ if (len > msg.data.length || offset > msg.data.length - len) revert Blocks.MalformedBlocks();
211
+ return msg.data[offset:offset + len];
212
+ }
213
+
214
+ /// @notice Return the complete validated command state without consuming it.
215
+ /// @dev Remains the complete original state after the state cursor advances.
216
+ function rawState(Execution memory exec) internal pure returns (bytes calldata) {
217
+ return raw(exec, Lanes.State);
218
+ }
219
+
220
+ /// @notice Return the complete validated endpoint input without consuming it.
221
+ /// @dev Remains the complete original input after the input cursor advances.
222
+ function rawInput(Execution memory exec) internal pure returns (bytes calldata) {
223
+ return raw(exec, Lanes.Input);
224
+ }
225
+
226
+ /// @notice Return whether the next block on `lane` has `key` and an empty payload.
227
+ /// @param exec Execution whose decoder is inspected without advancing.
228
+ /// @param lane Decoder lane to inspect.
229
+ /// @param key Expected block key.
230
+ /// @return Whether a complete matching empty block header occurs next.
231
+ function isEmpty(Execution memory exec, uint8 lane, bytes4 key) internal pure returns (bool) {
232
+ uint cur = exec.decoders.select(lane);
233
+ (uint i, uint offset, uint len) = cur.decode();
234
+ return Blocks.isEmpty(offset + i, offset + len, key);
235
+ }
236
+
190
237
  /// @notice Validate and consume the next block from an execution decoder lane.
191
238
  /// @param exec Execution whose selected decoder cursor is advanced over the complete block.
192
239
  /// @param lane Execution decoder lane to select.
@@ -199,6 +246,34 @@ library Executions {
199
246
  exec.decoders = cur.seekAbs(end);
200
247
  }
201
248
 
249
+ /// @notice Validate and consume one block from a decoder lane, returning its complete encoding.
250
+ /// @param exec Execution whose selected decoder cursor is advanced past the block.
251
+ /// @param lane Execution decoder lane to select.
252
+ /// @param key Expected block key.
253
+ /// @return data Calldata view of the complete block, including its header.
254
+ function takeBlock(
255
+ Execution memory exec,
256
+ uint8 lane,
257
+ bytes4 key
258
+ ) internal pure returns (bytes calldata data) {
259
+ (uint abs, uint end) = consume(exec, lane, Specs.create(key, 0, 0, 0));
260
+ data = msg.data[abs - Sizes.Header:end];
261
+ }
262
+
263
+ /// @notice Consume a matching empty block from an execution decoder lane when present.
264
+ /// @param exec Execution whose selected decoder advances only for a matching empty block.
265
+ /// @param lane Decoder lane to consume.
266
+ /// @param key Expected block key.
267
+ /// @return Whether an empty block was consumed.
268
+ function tryConsumeEmpty(Execution memory exec, uint8 lane, bytes4 key) internal pure returns (bool) {
269
+ uint cur = exec.decoders.select(lane);
270
+ (uint i, uint offset, uint size) = cur.decode();
271
+ (bytes4 current, uint len) = Blocks.peek(offset + i, offset + size);
272
+ if (current != key || len != 0) return false;
273
+ exec.decoders = cur.seek(i + Sizes.Header);
274
+ return true;
275
+ }
276
+
202
277
  /// @notice Validate and enter the payload of the next block in an execution decoder lane.
203
278
  /// @dev The selected lane remains in its existing frame so callers can decode
204
279
  /// child blocks in place. Callers should prove complete payload consumption
@@ -209,9 +284,48 @@ library Executions {
209
284
  /// @return abs Absolute position of the first payload byte.
210
285
  /// @return end Absolute position immediately after the payload.
211
286
  function enter(Execution memory exec, uint8 lane, uint spec) internal pure returns (uint abs, uint end) {
287
+ return enter(exec, lane, spec, 0);
288
+ }
289
+
290
+ /// @notice Validate a parent block and advance over a fixed payload prefix.
291
+ /// @dev `amount` is relative to the payload start and cannot exceed the
292
+ /// current parent payload. The returned `abs` remains the payload start.
293
+ /// @param exec Execution whose selected decoder advances over the header and fixed prefix.
294
+ /// @param lane Execution decoder lane to select.
295
+ /// @param spec Expected parent block specification.
296
+ /// @param amount Number of initial payload bytes to advance over.
297
+ /// @return abs Absolute position of the first payload byte.
298
+ /// @return end Absolute position immediately after the payload.
299
+ function enter(
300
+ Execution memory exec,
301
+ uint8 lane,
302
+ uint spec,
303
+ uint amount
304
+ ) internal pure returns (uint abs, uint end) {
212
305
  uint cur = exec.decoders.select(lane);
213
306
  (abs, end) = Blocks.expect(cur.absolute(), spec);
214
- exec.decoders = cur.seekAbs(abs);
307
+ if (amount > end - abs) revert Blocks.InvalidBlock();
308
+ exec.decoders = cur.seekAbs(abs + amount);
309
+ }
310
+
311
+ /// @notice Advance an execution decoder lane by a raw byte count.
312
+ /// @dev No block header or schema is validated.
313
+ /// @param exec Execution whose selected decoder cursor is advanced.
314
+ /// @param lane Decoder lane to select.
315
+ /// @param amount Number of bytes to advance.
316
+ function advance(Execution memory exec, uint8 lane, uint amount) internal pure {
317
+ uint cur = exec.decoders.select(lane);
318
+ exec.decoders = cur.advance(amount);
319
+ }
320
+
321
+ /// @notice Take a raw byte range from an execution decoder lane.
322
+ /// @dev No block header or schema is validated.
323
+ /// @param exec Execution whose selected decoder cursor is advanced.
324
+ /// @param lane Decoder lane to select.
325
+ /// @param amount Number of bytes to take.
326
+ /// @return abs Absolute position of the first taken byte.
327
+ function take(Execution memory exec, uint8 lane, uint amount) internal pure returns (uint abs) {
328
+ (exec.decoders, abs) = exec.decoders.consume(lane, amount);
215
329
  }
216
330
 
217
331
  /// @notice Require the active execution decoder to be at absolute position `abs`.
@@ -246,9 +360,7 @@ library Executions {
246
360
 
247
361
  /// @dev Return the next raw calldata word from `lane` and advance by `size` bytes.
248
362
  function next(Execution memory exec, uint8 lane, uint size) private pure returns (bytes32 value) {
249
- uint abs;
250
- (exec.decoders, abs) = exec.decoders.consume(lane, size);
251
- value = Blocks.read32(abs);
363
+ value = Blocks.read32(take(exec, lane, size));
252
364
  }
253
365
 
254
366
  /// @notice Return the next raw byte from a decoder lane and advance it by one byte.
@@ -806,6 +918,14 @@ library Executions {
806
918
  return reserve(exec, size, size);
807
919
  }
808
920
 
921
+ /// @notice Append an empty block to execution output.
922
+ /// @param exec Execution receiving the block.
923
+ /// @param key Block key.
924
+ function outputEmpty(Execution memory exec, bytes4 key) internal pure {
925
+ uint i = reserve(exec, Sizes.Header);
926
+ Blocks.writeEmpty(exec.output, i, key);
927
+ }
928
+
809
929
  /// @notice Append an ACCOUNT block to execution output.
810
930
  /// @param exec Execution receiving the block.
811
931
  /// @param account Account identifier to encode.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rootzero/contracts",
3
- "version": "1.21.0",
3
+ "version": "1.23.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",
@@ -9,8 +9,7 @@
9
9
  "**/*.sol",
10
10
  "README.md",
11
11
  "CHANGELOG.md",
12
- "LICENSE",
13
- "docs/Schema.md"
12
+ "LICENSE"
14
13
  ],
15
14
  "publishConfig": {
16
15
  "access": "public"