@rootzero/contracts 1.28.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/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) {
package/core/Pipeline.sol CHANGED
@@ -2,11 +2,11 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import {Blocks} from "../codec/Blocks.sol";
5
+ import {Keys} from "../codec/Keys.sol";
5
6
  import {TrustAccess} from "./Access.sol";
6
- import {rawCommandCall} from "./Calls.sol";
7
- import {Cursors} from "../utils/Cursors.sol";
8
7
  import {InsufficientValue, OutOfBounds, UnexpectedState} from "../utils/Errors.sol";
9
- import {unpackCommand} from "../utils/Nodes.sol";
8
+ import {Nodes} from "../utils/Nodes.sol";
9
+ import {Flags} from "../utils/Flags.sol";
10
10
 
11
11
  /// @notice Hook implemented by hosts that execute encoded step streams.
12
12
  abstract contract PipeHook {
@@ -21,31 +21,188 @@ abstract contract PipeHook {
21
21
 
22
22
  /// @notice Hook implemented by pipeline hosts that execute host-local commands.
23
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.
24
+ /// @notice Try to execute one command whose node ID targets the current host.
25
+ /// @dev Implementations returning `handled = true` are responsible for
26
+ /// authorizing the command. Return false without side effects to delegate to
27
+ /// the trusted normal external entrypoint. Handoff commands must be delegated
28
+ /// because this hook receives ordinary input without the continuation that
29
+ /// Pipeline adds to the RELAY envelope. Implementations may revert instead.
26
30
  function execute(
27
31
  uint cmd,
28
32
  bytes32 account,
29
33
  bytes memory state,
30
34
  bytes calldata input,
31
35
  uint value
32
- ) internal virtual returns (bytes memory output, uint credit);
36
+ ) internal virtual returns (bool handled, bytes memory output, uint credit);
33
37
  }
34
38
 
35
39
  /// @title Pipeline
36
40
  /// @notice Core pipeline functionality shared by higher-level surfaces.
41
+ /// @dev This gas-sensitive implementation intentionally inlines the command ID
42
+ /// layout and the CONTEXT, BYTES, and RELAY block encodings. Changes to command
43
+ /// selector, target, or flag placement, or to those block layouts, must update
44
+ /// the corresponding assembly and packed-cursor logic here.
37
45
  abstract contract Pipeline is TrustAccess, PipeHook, ExecuteHook {
38
- function run(
46
+ /// @dev Private pipeline cursor layout:
47
+ /// bits 0-31 current STEP offset, 32-63 stream end,
48
+ /// 64-95 command-input offset, 96-127 command-input length.
49
+ function unpackBytes(uint abs) private pure returns (uint input, uint end) {
50
+ uint body;
51
+ (body, end) = Blocks.expectKey(abs, Keys.Bytes);
52
+ // `expectKey` constructs `end` as `body + uint32(length)`.
53
+ unchecked {
54
+ input = uint32(body) | ((end - body) << 32);
55
+ }
56
+ }
57
+
58
+ function takeStep(uint cursor) private pure returns (uint cmd, uint value, uint updated) {
59
+ uint abs = uint32(cursor);
60
+ uint limit;
61
+ (abs, limit) = Blocks.expectKey(abs, Keys.Step);
62
+ assembly ("memory-safe") {
63
+ cmd := calldataload(abs)
64
+ value := calldataload(add(abs, 0x20))
65
+ }
66
+
67
+ (uint input, uint end) = unpackBytes(abs + 64);
68
+ if (end != limit) revert Blocks.InvalidBlock();
69
+ if (end > uint32(cursor >> 32)) revert OutOfBounds();
70
+
71
+ updated = uint32(end) | (uint64(cursor) & (uint(type(uint32).max) << 32)) | (input << 64);
72
+ }
73
+
74
+ function rawInput(uint cursor) private pure returns (bytes calldata input) {
75
+ assembly ("memory-safe") {
76
+ input.offset := and(shr(64, cursor), 0xffffffff)
77
+ input.length := and(shr(96, cursor), 0xffffffff)
78
+ }
79
+ }
80
+
81
+ /// @dev Execute the prepared command call and strictly decode `(bytes, uint)`.
82
+ function invokeCommand(
39
83
  uint cmd,
84
+ uint value,
40
85
  bytes32 account,
41
86
  bytes memory state,
42
- bytes calldata input,
43
- uint value
87
+ uint input
44
88
  ) 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);
89
+ assembly ("memory-safe") {
90
+ let scratch := mload(0x40)
91
+ let ctxlen
92
+ switch iszero(shr(64, input))
93
+ case 1 {
94
+ let statelen := mload(state)
95
+ let inputlen := and(shr(32, input), 0xffffffff)
96
+ ctxlen := add(56, add(statelen, inputlen))
97
+
98
+ mstore(scratch, shl(224, and(shr(160, cmd), 0xffffffff)))
99
+ mstore(add(scratch, 0x04), 0x20)
100
+ mstore(add(scratch, 0x24), ctxlen)
101
+
102
+ let p := add(scratch, 0x44)
103
+ mstore(p, or(shl(224, 0xc5769e23), shl(192, sub(ctxlen, 8))))
104
+ mstore(add(p, 0x08), account)
105
+ p := add(p, 0x28)
106
+ mstore(p, or(shl(224, 0x6911b332), shl(192, statelen)))
107
+ mcopy(add(p, 0x08), add(state, 0x20), statelen)
108
+ p := add(add(p, 0x08), statelen)
109
+ mstore(p, or(shl(224, 0x6911b332), shl(192, inputlen)))
110
+ calldatacopy(add(p, 0x08), and(input, 0xffffffff), inputlen)
111
+ }
112
+ default {
113
+ let statelen := mload(state)
114
+ let inputlen := and(shr(96, input), 0xffffffff)
115
+ let stepslen := sub(and(shr(32, input), 0xffffffff), and(input, 0xffffffff))
116
+ let relaylen := add(16, add(inputlen, stepslen))
117
+ ctxlen := add(64, add(statelen, relaylen))
118
+
119
+ mstore(scratch, shl(224, and(shr(160, cmd), 0xffffffff)))
120
+ mstore(add(scratch, 0x04), 0x20)
121
+ mstore(add(scratch, 0x24), ctxlen)
122
+
123
+ let p := add(scratch, 0x44)
124
+ mstore(p, or(shl(224, 0xc5769e23), shl(192, sub(ctxlen, 8))))
125
+ mstore(add(p, 0x08), account)
126
+ p := add(p, 0x28)
127
+ mstore(p, or(shl(224, 0x6911b332), shl(192, statelen)))
128
+ mcopy(add(p, 0x08), add(state, 0x20), statelen)
129
+
130
+ p := add(add(p, 0x08), statelen)
131
+ mstore(p, or(shl(224, 0x6911b332), shl(192, add(8, relaylen))))
132
+ p := add(p, 0x08)
133
+ mstore(p, or(shl(224, 0xc34cc52a), shl(192, relaylen)))
134
+ p := add(p, 0x08)
135
+ mstore(p, or(shl(224, 0x6911b332), shl(192, inputlen)))
136
+ calldatacopy(add(p, 0x08), and(shr(64, input), 0xffffffff), inputlen)
137
+ p := add(add(p, 0x08), inputlen)
138
+ mstore(p, or(shl(224, 0x6911b332), shl(192, stepslen)))
139
+ calldatacopy(add(p, 0x08), and(input, 0xffffffff), stepslen)
140
+ }
141
+ ctxlen := add(68, and(add(ctxlen, 0x1f), not(0x1f)))
142
+ let inputend := and(add(add(scratch, ctxlen), 0x1f), not(0x1f))
143
+ let success := call(
144
+ gas(),
145
+ and(cmd, 0xffffffffffffffffffffffffffffffffffffffff),
146
+ value,
147
+ scratch,
148
+ ctxlen,
149
+ 0,
150
+ 0
151
+ )
152
+ let retlen := returndatasize()
153
+ if iszero(success) {
154
+ mstore(scratch, shl(224, 0x20577b07))
155
+ mstore(add(scratch, 0x04), and(cmd, 0xffffffffffffffffffffffffffffffffffffffff))
156
+ mstore(add(scratch, 0x24), shl(224, and(shr(160, cmd), 0xffffffff)))
157
+ mstore(add(scratch, 0x44), 0x60)
158
+ mstore(add(scratch, 0x64), retlen)
159
+ mstore(add(add(scratch, 0x84), retlen), 0)
160
+ returndatacopy(add(scratch, 0x84), 0, retlen)
161
+ revert(scratch, add(0x84, and(add(retlen, 0x1f), not(0x1f))))
162
+ }
163
+
164
+ if lt(retlen, 0x60) {
165
+ revert(0, 0)
166
+ }
167
+ returndatacopy(scratch, 0, retlen)
168
+ if iszero(eq(mload(scratch), 0x40)) {
169
+ revert(0, 0)
170
+ }
171
+ let len1 := mload(add(scratch, 0x40))
172
+ if gt(len1, sub(retlen, 0x60)) {
173
+ revert(0, 0)
174
+ }
175
+ let pad1 := and(add(len1, 0x1f), not(0x1f))
176
+ if iszero(eq(retlen, add(0x60, pad1))) {
177
+ revert(0, 0)
178
+ }
179
+ output := add(scratch, 0x40)
180
+ credit := mload(add(scratch, 0x20))
181
+
182
+ let retend := and(add(add(scratch, retlen), 0x1f), not(0x1f))
183
+ if gt(retend, inputend) {
184
+ inputend := retend
185
+ }
186
+ mstore(0x40, inputend)
187
+ }
188
+ }
189
+
190
+ function run(
191
+ uint cmd,
192
+ bytes32 account,
193
+ bytes memory state,
194
+ uint value,
195
+ uint cursor
196
+ ) private returns (bytes memory output, uint credit, uint updated) {
197
+ if (address(uint160(cmd)) == address(this)) {
198
+ bool handled;
199
+ (handled, output, credit) = execute(cmd, account, state, rawInput(cursor), value);
200
+ if (handled) return (output, credit, cursor);
201
+ }
202
+ ensureTrusted(Nodes.command(cmd));
203
+ bool handoff = uint8(cmd >> 224) & Flags.Handoff != 0;
204
+ (output, credit) = invokeCommand(cmd, value, account, state, handoff ? cursor : cursor >> 64);
205
+ updated = handoff ? (cursor & ~uint(type(uint32).max)) | uint32(cursor >> 32) : cursor;
49
206
  }
50
207
 
51
208
  /// @notice Execute a STEP block stream through the pipeline.
@@ -62,18 +219,21 @@ abstract contract Pipeline is TrustAccess, PipeHook, ExecuteHook {
62
219
  bytes calldata steps,
63
220
  uint budget
64
221
  ) internal virtual override returns (uint remaining) {
65
- (uint abs, uint end) = Cursors.bounds(steps);
222
+ uint cursor;
223
+ assembly ("memory-safe") {
224
+ cursor := or(steps.offset, shl(32, add(steps.offset, steps.length)))
225
+ }
66
226
 
67
- while (abs < end) {
68
- (uint cmd, uint value, bytes calldata input, uint next) = Blocks.unpackStep(abs);
69
- if (next > end) revert OutOfBounds();
227
+ while (uint32(cursor) < uint32(cursor >> 32)) {
228
+ uint cmd;
229
+ uint value;
230
+ (cmd, value, cursor) = takeStep(cursor);
70
231
  if (value > budget) revert InsufficientValue();
71
232
  unchecked {
72
233
  budget -= value;
73
234
  }
74
- (state, value) = run(cmd, account, state, input, value);
235
+ (state, value, cursor) = run(cmd, account, state, value, cursor);
75
236
  budget += value;
76
- abs = next;
77
237
  }
78
238
 
79
239
  if (state.length != 0) revert UnexpectedState();
@@ -44,7 +44,7 @@ abstract contract SettleHook {
44
44
 
45
45
  /// @title Settlement
46
46
  /// @notice Default account-hook implementation for transaction posting and position settlement.
47
- abstract contract Settlement is PostHook, SettleHook, RepayHook, DebitAccountHook, CreditAccountHook {
47
+ abstract contract Settlement is PostHook, DebitAccountHook, CreditAccountHook, SettleHook, RepayHook {
48
48
  /// @notice Post one transaction by debiting its source and crediting its destination.
49
49
  /// Returns without calling either hook when `amount` is zero and skips either
50
50
  /// operation when the corresponding account is zero.