@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.
- package/CHANGELOG.md +48 -0
- package/Core.sol +2 -2
- package/Endpoints.sol +2 -2
- package/README.md +34 -20
- package/Utils.sol +1 -1
- package/codec/Blocks.sol +13 -13
- package/codec/Decoders.sol +1 -1
- package/codec/Keys.sol +1 -1
- package/codec/Schema.sol +8 -5
- package/codec/Specs.sol +3 -4
- package/codec/Writers.sol +2 -2
- package/commands/Bootstrap.sol +31 -45
- package/commands/Cashout.sol +3 -2
- package/commands/Credit.sol +3 -2
- package/commands/Debit.sol +3 -2
- package/commands/Recover.sol +2 -1
- package/commands/Relay.sol +3 -2
- package/commands/Repay.sol +3 -2
- package/commands/Settle.sol +3 -2
- package/commands/admin/Execute.sol +3 -1
- package/core/Access.sol +2 -3
- package/core/Calls.sol +83 -79
- package/core/Pipeline.sol +31 -27
- package/core/Portal.sol +2 -2
- package/core/Settlement.sol +2 -1
- package/core/Types.sol +1 -0
- package/execution/Budget.sol +8 -4
- package/execution/Execution.sol +8 -9
- package/package.json +1 -1
- package/ports/Dispatch.sol +3 -2
- package/utils/Accounts.sol +16 -7
- package/utils/Errors.sol +6 -0
- package/utils/Nodes.sol +15 -0
package/commands/Settle.sol
CHANGED
|
@@ -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
|
-
|
|
112
|
+
uint value
|
|
112
113
|
) internal returns (bytes memory, uint) {
|
|
113
114
|
if (value != 0) revert ValueNotAllowed();
|
|
114
|
-
if (input.length != 0) revert
|
|
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
|
|
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 (
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
42
|
-
)
|
|
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
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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,
|
|
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,
|
|
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];
|
package/core/Settlement.sol
CHANGED
|
@@ -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;
|
package/execution/Budget.sol
CHANGED
|
@@ -38,15 +38,19 @@ library Budgets {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/// @notice Deduct the EVM value lane of `resources` from `budget`.
|
|
41
|
-
/// @dev
|
|
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 (
|
|
46
|
-
|
|
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,
|
|
61
|
+
) internal pure returns (uint remaining, uint value) {
|
|
58
62
|
value = uint128(resources);
|
|
59
63
|
remaining = useValue(budget, value);
|
|
60
64
|
}
|
package/execution/Execution.sol
CHANGED
|
@@ -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,
|
|
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,
|
|
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,
|
|
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
|
|
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 (
|
|
1258
|
-
|
|
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
package/ports/Dispatch.sol
CHANGED
|
@@ -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
|
|
17
|
-
///
|
|
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.
|
package/utils/Accounts.sol
CHANGED
|
@@ -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
|
|
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 (!
|
|
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
|
|
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 (
|
|
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
|
|
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 (
|
|
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
|
|
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
|
///
|