@rootzero/contracts 1.24.0 → 1.25.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 +39 -0
- package/Core.sol +1 -1
- package/Endpoints.sol +1 -0
- package/README.md +8 -3
- package/codec/Blocks.sol +79 -21
- package/codec/Decoders.sol +24 -4
- package/commands/Base.sol +1 -4
- package/commands/Debit.sol +2 -5
- package/commands/admin/Base.sol +1 -4
- package/core/Pipeline.sol +26 -7
- package/execution/Budget.sol +22 -0
- package/execution/Execution.sol +24 -3
- package/package.json +1 -1
- package/ports/Pipe.sol +4 -5
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,45 @@ sections are immutable and must continue to describe the tagged release.
|
|
|
8
8
|
|
|
9
9
|
## Unreleased
|
|
10
10
|
|
|
11
|
+
## 1.25.0
|
|
12
|
+
|
|
13
|
+
### Breaking Changes
|
|
14
|
+
|
|
15
|
+
- Changed `Pipeline.pipe` to accept a scalar `uint` budget and return the
|
|
16
|
+
remaining scalar budget. The standalone mutable `Budget` type remains
|
|
17
|
+
available for other funded execution flows.
|
|
18
|
+
- Split the pipeline hook from its standard implementation. `PipePayablePort`
|
|
19
|
+
now inherits only `PipeHook`; hosts that want the built-in pipeline must
|
|
20
|
+
compose `Pipeline` explicitly, while custom hosts may implement the hook
|
|
21
|
+
without inheriting the standard dispatch and posting implementation.
|
|
22
|
+
- Changed `Decoders.open` to wrap the complete non-empty calldata source as an
|
|
23
|
+
ungrouped cursor. Code that needs the former counted first-homogeneous-run
|
|
24
|
+
behavior must use the new `Decoders.batch` helper.
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- Added scalar `Budgets.useValue` and `Budgets.useResourceValue` overloads, and
|
|
29
|
+
`Executions.drainBudget` for transferring an execution budget as a `uint`.
|
|
30
|
+
- Added `PipeHook` to the `Core.sol` and `Endpoints.sol` package barrels.
|
|
31
|
+
- Added `Blocks.expectKey` and key-only `Decoders.consume` and
|
|
32
|
+
`Executions.consume` overloads for validating blocks whose payload shape is
|
|
33
|
+
established by specialized decoding logic.
|
|
34
|
+
- Added `Blocks.require1`, `require2`, `require4`, `require8`, and `require16`,
|
|
35
|
+
completing the fixed-width validation family alongside `require32`.
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
|
|
39
|
+
- Specialized built-in composite block unpackers to validate their known keys
|
|
40
|
+
directly instead of constructing and evaluating generic specifications.
|
|
41
|
+
- Specialized fixed-width block validation, empty-block inspection, and
|
|
42
|
+
key-only `takeBlock` paths to avoid redundant specification and calldata
|
|
43
|
+
work.
|
|
44
|
+
- Pipeline execution now validates the complete STEP source and rejects a
|
|
45
|
+
trailing block with a different key instead of silently stopping after the
|
|
46
|
+
first homogeneous run.
|
|
47
|
+
- Simplified internal debit-account output allocation to use the fixed-width
|
|
48
|
+
input size directly.
|
|
49
|
+
|
|
11
50
|
## 1.24.0
|
|
12
51
|
|
|
13
52
|
### Breaking Changes
|
package/Core.sol
CHANGED
|
@@ -14,7 +14,7 @@ import { NativeAsset, Runtime } from "./core/Runtime.sol";
|
|
|
14
14
|
import { Admins, CommandHost, Guardians, Host, HostIntroduction, IHostIntroduction } from "./core/Host.sol";
|
|
15
15
|
import { CommandCalls, FailedCall, NodeCalls, PortCalls, RawNodeCalls } from "./core/Calls.sol";
|
|
16
16
|
import { EndpointBase, InputEndpointBase } from "./core/Endpoint.sol";
|
|
17
|
-
import { Pipeline } from "./core/Pipeline.sol";
|
|
17
|
+
import { PipeHook, Pipeline } from "./core/Pipeline.sol";
|
|
18
18
|
import { Budget, Budgets } from "./execution/Budget.sol";
|
|
19
19
|
import { CreditAccountHook, DebitAccountHook, PostHook, RepayHook, SettleHook, Settlement } from "./core/Settlement.sol";
|
|
20
20
|
import { Portal } from "./core/Portal.sol";
|
package/Endpoints.sol
CHANGED
|
@@ -6,6 +6,7 @@ pragma solidity ^0.8.33;
|
|
|
6
6
|
|
|
7
7
|
// Shared endpoint hooks
|
|
8
8
|
import {Flags} from "./codec/Descriptors.sol";
|
|
9
|
+
import {PipeHook} from "./core/Pipeline.sol";
|
|
9
10
|
import {CreditAccountHook, DebitAccountHook, PostHook, RepayHook, SettleHook} from "./core/Settlement.sol";
|
|
10
11
|
|
|
11
12
|
// Commands
|
package/README.md
CHANGED
|
@@ -366,13 +366,15 @@ running the next step. This is the core of `Pipeline.pipe`:
|
|
|
366
366
|
```solidity
|
|
367
367
|
while (cur.more()) {
|
|
368
368
|
(uint cmd, uint resources, bytes calldata input) = cur.unpackStep();
|
|
369
|
+
uint128 value;
|
|
370
|
+
(budget, value) = Budgets.useResourceValue(budget, resources);
|
|
369
371
|
Reader memory transactions;
|
|
370
372
|
(state, transactions.source) = dispatch(
|
|
371
373
|
cmd,
|
|
372
374
|
account,
|
|
373
375
|
state,
|
|
374
376
|
input,
|
|
375
|
-
|
|
377
|
+
value
|
|
376
378
|
);
|
|
377
379
|
while (transactions.more()) {
|
|
378
380
|
(bytes32 from, bytes32 to, bytes32 asset, uint amount) = transactions.unpackTransaction();
|
|
@@ -382,6 +384,9 @@ while (cur.more()) {
|
|
|
382
384
|
if (state.length != 0) revert UnexpectedState();
|
|
383
385
|
```
|
|
384
386
|
|
|
387
|
+
`Pipeline.pipe` takes the available native-value budget as a `uint` and returns
|
|
388
|
+
the remaining budget after every step has executed.
|
|
389
|
+
|
|
385
390
|
A transfer, for instance, is a two-step pipeline: `debitAccount` turns an
|
|
386
391
|
`#amount` input into `#balance` state, and `payout` consumes that state
|
|
387
392
|
toward a recipient. Because a pipeline is just blocks, it is also the unit of
|
|
@@ -482,11 +487,11 @@ names, access sets, balances — from logs alone, with no artifact files.
|
|
|
482
487
|
Import from the package entry points rather than deep paths:
|
|
483
488
|
|
|
484
489
|
- `@rootzero/contracts/Core.sol` — `Host`, access control, `Balances`,
|
|
485
|
-
`Settlement`, `Pipeline`, `Portal`, validator
|
|
490
|
+
`Settlement`, `PipeHook`, `Pipeline`, `Portal`, validator
|
|
486
491
|
- `@rootzero/contracts/Commands.sol` — `CommandBase`, `Execution`, `Flags`,
|
|
487
492
|
codec helpers, and shared value types for authoring custom commands
|
|
488
493
|
- `@rootzero/contracts/Endpoints.sol` — command, admin, port, guard, and query
|
|
489
|
-
mixins, their hooks, and `Flags`
|
|
494
|
+
mixins, their hooks (including `PipeHook`), and `Flags`
|
|
490
495
|
- `@rootzero/contracts/Codec.sol` — `Blocks`, calldata `Cur`/`Cursors`, memory
|
|
491
496
|
`Reader`/`Readers`, `Writers`, `Schemas`, `Descriptors`, `Flags`, `Keys`, and
|
|
492
497
|
`Specs`
|
package/codec/Blocks.sol
CHANGED
|
@@ -100,16 +100,33 @@ library Blocks {
|
|
|
100
100
|
end = body + len;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
/// @notice Validate a known block key and return its payload bounds.
|
|
104
|
+
/// @dev DANGER: This performs an unchecked calldata read and validates only
|
|
105
|
+
/// the key. The caller must validate the known payload shape and returned end.
|
|
106
|
+
/// @param abs Absolute calldata position of the header.
|
|
107
|
+
/// @param key Expected block key.
|
|
108
|
+
/// @return body Absolute position of the first payload byte.
|
|
109
|
+
/// @return end Absolute position immediately after the payload.
|
|
110
|
+
function expectKey(uint abs, bytes4 key) internal pure returns (uint body, uint end) {
|
|
111
|
+
uint len = header(abs, key);
|
|
112
|
+
unchecked {
|
|
113
|
+
body = abs + Sizes.Header;
|
|
114
|
+
end = body + len;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
103
118
|
/// @dev Validate the key and exact payload size of a fixed-width block.
|
|
104
119
|
/// @param abs Absolute calldata position of the header.
|
|
105
|
-
/// @param
|
|
120
|
+
/// @param key Expected block key.
|
|
106
121
|
/// @param size Expected payload length.
|
|
107
122
|
/// @return body Absolute position of the payload.
|
|
108
123
|
/// @return end Absolute position after the payload.
|
|
109
|
-
function expectFixed(uint abs,
|
|
110
|
-
if (header(abs,
|
|
111
|
-
|
|
112
|
-
|
|
124
|
+
function expectFixed(uint abs, bytes4 key, uint size) private pure returns (uint body, uint end) {
|
|
125
|
+
if (header(abs, key) != size) revert InvalidBlock();
|
|
126
|
+
unchecked {
|
|
127
|
+
body = abs + Sizes.Header;
|
|
128
|
+
end = body + size;
|
|
129
|
+
}
|
|
113
130
|
}
|
|
114
131
|
|
|
115
132
|
/// @notice Validate an empty block at an absolute calldata position.
|
|
@@ -139,8 +156,9 @@ library Blocks {
|
|
|
139
156
|
/// @param key Expected block key.
|
|
140
157
|
/// @return Whether the expected key occurs with a zero-length payload.
|
|
141
158
|
function isEmpty(uint abs, uint end, bytes4 key) internal pure returns (bool) {
|
|
142
|
-
if (
|
|
143
|
-
|
|
159
|
+
if (abs > end || Sizes.Header > end - abs) return false;
|
|
160
|
+
uint head = uint(read32(abs));
|
|
161
|
+
return uint32(head >> 224) == uint32(key) && uint32(head >> 192) == 0;
|
|
144
162
|
}
|
|
145
163
|
|
|
146
164
|
/// @notice Find the first block with `key` at or after absolute position `abs`.
|
|
@@ -1080,6 +1098,46 @@ library Blocks {
|
|
|
1080
1098
|
}
|
|
1081
1099
|
}
|
|
1082
1100
|
|
|
1101
|
+
/// @notice Require the byte at an absolute calldata position to match `expected`.
|
|
1102
|
+
/// @dev DANGER: Unchecked calldata read. Values beyond calldata are zero-padded.
|
|
1103
|
+
/// @param abs Absolute calldata position.
|
|
1104
|
+
/// @param expected Expected byte.
|
|
1105
|
+
function require1(uint abs, bytes1 expected) internal pure {
|
|
1106
|
+
if (read1(abs) != expected) revert UnexpectedValue();
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
/// @notice Require the two bytes at an absolute calldata position to match `expected`.
|
|
1110
|
+
/// @dev DANGER: Unchecked calldata read. Values beyond calldata are zero-padded.
|
|
1111
|
+
/// @param abs Absolute calldata position.
|
|
1112
|
+
/// @param expected Expected two-byte value.
|
|
1113
|
+
function require2(uint abs, bytes2 expected) internal pure {
|
|
1114
|
+
if (read2(abs) != expected) revert UnexpectedValue();
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
/// @notice Require the four bytes at an absolute calldata position to match `expected`.
|
|
1118
|
+
/// @dev DANGER: Unchecked calldata read. Values beyond calldata are zero-padded.
|
|
1119
|
+
/// @param abs Absolute calldata position.
|
|
1120
|
+
/// @param expected Expected four-byte value.
|
|
1121
|
+
function require4(uint abs, bytes4 expected) internal pure {
|
|
1122
|
+
if (read4(abs) != expected) revert UnexpectedValue();
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
/// @notice Require the eight bytes at an absolute calldata position to match `expected`.
|
|
1126
|
+
/// @dev DANGER: Unchecked calldata read. Values beyond calldata are zero-padded.
|
|
1127
|
+
/// @param abs Absolute calldata position.
|
|
1128
|
+
/// @param expected Expected eight-byte value.
|
|
1129
|
+
function require8(uint abs, bytes8 expected) internal pure {
|
|
1130
|
+
if (read8(abs) != expected) revert UnexpectedValue();
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
/// @notice Require the sixteen bytes at an absolute calldata position to match `expected`.
|
|
1134
|
+
/// @dev DANGER: Unchecked calldata read. Values beyond calldata are zero-padded.
|
|
1135
|
+
/// @param abs Absolute calldata position.
|
|
1136
|
+
/// @param expected Expected sixteen-byte value.
|
|
1137
|
+
function require16(uint abs, bytes16 expected) internal pure {
|
|
1138
|
+
if (read16(abs) != expected) revert UnexpectedValue();
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1083
1141
|
/// @notice Require the word at an absolute calldata position to match `expected`.
|
|
1084
1142
|
/// @dev DANGER: Unchecked calldata read. Values beyond calldata are zero-padded.
|
|
1085
1143
|
/// @param abs Absolute calldata position.
|
|
@@ -1106,7 +1164,7 @@ library Blocks {
|
|
|
1106
1164
|
/// @return a First payload word.
|
|
1107
1165
|
/// @return end Absolute position after the block.
|
|
1108
1166
|
function unpack32(uint abs, uint spec) internal pure returns (bytes32 a, uint end) {
|
|
1109
|
-
(abs, end) = expectFixed(abs, spec, 32);
|
|
1167
|
+
(abs, end) = expectFixed(abs, Specs.key(spec), 32);
|
|
1110
1168
|
a = read32(abs);
|
|
1111
1169
|
}
|
|
1112
1170
|
|
|
@@ -1117,7 +1175,7 @@ library Blocks {
|
|
|
1117
1175
|
/// @return b Second payload word.
|
|
1118
1176
|
/// @return end Absolute position after the block.
|
|
1119
1177
|
function unpack64(uint abs, uint spec) internal pure returns (bytes32 a, bytes32 b, uint end) {
|
|
1120
|
-
(abs, end) = expectFixed(abs, spec, 64);
|
|
1178
|
+
(abs, end) = expectFixed(abs, Specs.key(spec), 64);
|
|
1121
1179
|
assembly ("memory-safe") {
|
|
1122
1180
|
a := calldataload(abs)
|
|
1123
1181
|
b := calldataload(add(abs, 0x20))
|
|
@@ -1135,7 +1193,7 @@ library Blocks {
|
|
|
1135
1193
|
uint abs,
|
|
1136
1194
|
uint spec
|
|
1137
1195
|
) internal pure returns (bytes32 a, bytes32 b, bytes32 c, uint end) {
|
|
1138
|
-
(abs, end) = expectFixed(abs, spec, 96);
|
|
1196
|
+
(abs, end) = expectFixed(abs, Specs.key(spec), 96);
|
|
1139
1197
|
assembly ("memory-safe") {
|
|
1140
1198
|
a := calldataload(abs)
|
|
1141
1199
|
b := calldataload(add(abs, 0x20))
|
|
@@ -1155,7 +1213,7 @@ library Blocks {
|
|
|
1155
1213
|
uint abs,
|
|
1156
1214
|
uint spec
|
|
1157
1215
|
) internal pure returns (bytes32 a, bytes32 b, bytes32 c, bytes32 d, uint end) {
|
|
1158
|
-
(abs, end) = expectFixed(abs, spec, 128);
|
|
1216
|
+
(abs, end) = expectFixed(abs, Specs.key(spec), 128);
|
|
1159
1217
|
assembly ("memory-safe") {
|
|
1160
1218
|
a := calldataload(abs)
|
|
1161
1219
|
b := calldataload(add(abs, 0x20))
|
|
@@ -1177,7 +1235,7 @@ library Blocks {
|
|
|
1177
1235
|
uint abs,
|
|
1178
1236
|
uint spec
|
|
1179
1237
|
) internal pure returns (bytes32 a, bytes32 b, bytes32 c, bytes32 d, bytes32 e, uint end) {
|
|
1180
|
-
(abs, end) = expectFixed(abs, spec, 160);
|
|
1238
|
+
(abs, end) = expectFixed(abs, Specs.key(spec), 160);
|
|
1181
1239
|
assembly ("memory-safe") {
|
|
1182
1240
|
a := calldataload(abs)
|
|
1183
1241
|
b := calldataload(add(abs, 0x20))
|
|
@@ -1653,7 +1711,7 @@ library Blocks {
|
|
|
1653
1711
|
uint abs
|
|
1654
1712
|
) internal pure returns (uint entity, bytes calldata stream, uint end) {
|
|
1655
1713
|
uint limit;
|
|
1656
|
-
(abs, limit) =
|
|
1714
|
+
(abs, limit) = expectKey(abs, Keys.Annotation);
|
|
1657
1715
|
assembly ("memory-safe") {
|
|
1658
1716
|
entity := calldataload(abs)
|
|
1659
1717
|
}
|
|
@@ -1671,7 +1729,7 @@ library Blocks {
|
|
|
1671
1729
|
uint abs
|
|
1672
1730
|
) internal pure returns (bytes32 account, bytes calldata state, bytes calldata input, uint end) {
|
|
1673
1731
|
uint limit;
|
|
1674
|
-
(abs, limit) =
|
|
1732
|
+
(abs, limit) = expectKey(abs, Keys.Context);
|
|
1675
1733
|
assembly ("memory-safe") {
|
|
1676
1734
|
account := calldataload(abs)
|
|
1677
1735
|
}
|
|
@@ -1692,7 +1750,7 @@ library Blocks {
|
|
|
1692
1750
|
uint abs
|
|
1693
1751
|
) internal pure returns (uint cmd, uint resources, bytes calldata input, uint end) {
|
|
1694
1752
|
uint limit;
|
|
1695
|
-
(abs, limit) =
|
|
1753
|
+
(abs, limit) = expectKey(abs, Keys.Step);
|
|
1696
1754
|
assembly ("memory-safe") {
|
|
1697
1755
|
cmd := calldataload(abs)
|
|
1698
1756
|
resources := calldataload(add(abs, 0x20))
|
|
@@ -1711,7 +1769,7 @@ library Blocks {
|
|
|
1711
1769
|
uint abs
|
|
1712
1770
|
) internal pure returns (uint target, uint resources, bytes calldata payload, uint end) {
|
|
1713
1771
|
uint limit;
|
|
1714
|
-
(abs, limit) =
|
|
1772
|
+
(abs, limit) = expectKey(abs, Keys.Call);
|
|
1715
1773
|
assembly ("memory-safe") {
|
|
1716
1774
|
target := calldataload(abs)
|
|
1717
1775
|
resources := calldataload(add(abs, 0x20))
|
|
@@ -1730,7 +1788,7 @@ library Blocks {
|
|
|
1730
1788
|
uint abs
|
|
1731
1789
|
) internal pure returns (uint portal, uint resources, bytes calldata input, uint end) {
|
|
1732
1790
|
uint limit;
|
|
1733
|
-
(abs, limit) =
|
|
1791
|
+
(abs, limit) = expectKey(abs, Keys.Relay);
|
|
1734
1792
|
assembly ("memory-safe") {
|
|
1735
1793
|
portal := calldataload(abs)
|
|
1736
1794
|
resources := calldataload(add(abs, 0x20))
|
|
@@ -1749,7 +1807,7 @@ library Blocks {
|
|
|
1749
1807
|
uint abs
|
|
1750
1808
|
) internal pure returns (uint portal, uint resources, bytes calldata payload, uint end) {
|
|
1751
1809
|
uint limit;
|
|
1752
|
-
(abs, limit) =
|
|
1810
|
+
(abs, limit) = expectKey(abs, Keys.Dispatch);
|
|
1753
1811
|
assembly ("memory-safe") {
|
|
1754
1812
|
portal := calldataload(abs)
|
|
1755
1813
|
resources := calldataload(add(abs, 0x20))
|
|
@@ -1765,7 +1823,7 @@ library Blocks {
|
|
|
1765
1823
|
/// @return end Absolute position after the block.
|
|
1766
1824
|
function unpackLabel(uint abs) internal pure returns (bytes32 namespace, string memory name, uint end) {
|
|
1767
1825
|
uint limit;
|
|
1768
|
-
(abs, limit) =
|
|
1826
|
+
(abs, limit) = expectKey(abs, Keys.Label);
|
|
1769
1827
|
assembly ("memory-safe") {
|
|
1770
1828
|
namespace := calldataload(abs)
|
|
1771
1829
|
}
|
|
@@ -1783,7 +1841,7 @@ library Blocks {
|
|
|
1783
1841
|
/// @return end Absolute position after the block.
|
|
1784
1842
|
function unpackSchema(uint abs) internal pure returns (uint spec, string memory body, bytes32 name, uint end) {
|
|
1785
1843
|
uint limit;
|
|
1786
|
-
(abs, limit) =
|
|
1844
|
+
(abs, limit) = expectKey(abs, Keys.Schema);
|
|
1787
1845
|
assembly ("memory-safe") {
|
|
1788
1846
|
spec := calldataload(abs)
|
|
1789
1847
|
}
|
|
@@ -1810,7 +1868,7 @@ library Blocks {
|
|
|
1810
1868
|
uint abs
|
|
1811
1869
|
) internal pure returns (uint handler, uint resources, bytes32 key, bytes calldata witness, uint end) {
|
|
1812
1870
|
uint limit;
|
|
1813
|
-
(abs, limit) =
|
|
1871
|
+
(abs, limit) = expectKey(abs, Keys.Recover);
|
|
1814
1872
|
assembly ("memory-safe") {
|
|
1815
1873
|
handler := calldataload(abs)
|
|
1816
1874
|
resources := calldataload(add(abs, 0x20))
|
package/codec/Decoders.sol
CHANGED
|
@@ -32,10 +32,18 @@ library Decoders {
|
|
|
32
32
|
cur.state = Cursors.wrap(source[i:], 0, 0);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
/// @notice Open
|
|
36
|
-
/// @param source Calldata
|
|
37
|
-
/// @return cur Cursor spanning the
|
|
35
|
+
/// @notice Open a non-empty calldata source as an ungrouped cursor.
|
|
36
|
+
/// @param source Calldata region to open.
|
|
37
|
+
/// @return cur Cursor spanning the complete source.
|
|
38
38
|
function open(bytes calldata source) internal pure returns (Cur memory cur) {
|
|
39
|
+
if (source.length == 0) revert Blocks.EmptyRun();
|
|
40
|
+
cur.state = Cursors.wrap(source, 0, 0);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/// @notice Open the first homogeneous batch in `source`.
|
|
44
|
+
/// @param source Calldata block stream to open.
|
|
45
|
+
/// @return cur Counted cursor spanning the first homogeneous batch.
|
|
46
|
+
function batch(bytes calldata source) internal pure returns (Cur memory cur) {
|
|
39
47
|
(uint abs, uint limit) = Cursors.bounds(source);
|
|
40
48
|
if (abs == limit) revert Blocks.EmptyRun();
|
|
41
49
|
|
|
@@ -69,6 +77,18 @@ library Decoders {
|
|
|
69
77
|
cur.state = cur.state.seekAbs(end);
|
|
70
78
|
}
|
|
71
79
|
|
|
80
|
+
/// @notice Validate a known key and consume the next block from a cursor.
|
|
81
|
+
/// @dev Validates no payload-size constraint beyond proving the complete block
|
|
82
|
+
/// lies within the cursor's logical region.
|
|
83
|
+
/// @param cur Cursor advanced over the complete block.
|
|
84
|
+
/// @param key Expected block key.
|
|
85
|
+
/// @return abs Absolute position of the first payload byte.
|
|
86
|
+
/// @return end Absolute position immediately after the payload.
|
|
87
|
+
function consume(Cur memory cur, bytes4 key) internal pure returns (uint abs, uint end) {
|
|
88
|
+
(abs, end) = Blocks.expectKey(cur.state.absolute(), key);
|
|
89
|
+
cur.state = cur.state.seekAbs(end);
|
|
90
|
+
}
|
|
91
|
+
|
|
72
92
|
/// @notice Consume a matching empty block from a cursor when present.
|
|
73
93
|
/// @param cur Cursor advanced only when the matching block is empty.
|
|
74
94
|
/// @param key Expected block key.
|
|
@@ -268,7 +288,7 @@ library Decoders {
|
|
|
268
288
|
/// @return out Cursor spanning the complete encoded block.
|
|
269
289
|
function takeBlock(Cur memory cur, bytes4 key) internal pure returns (Cur memory out) {
|
|
270
290
|
uint abs = cur.state.absolute();
|
|
271
|
-
(, uint end) = consume(cur,
|
|
291
|
+
(, uint end) = consume(cur, key);
|
|
272
292
|
out.state = Cursors.create(abs, end - abs, 0, 0, 0);
|
|
273
293
|
}
|
|
274
294
|
|
package/commands/Base.sol
CHANGED
|
@@ -99,10 +99,7 @@ abstract contract CommandBase is CallerAccess, EndpointBase, ReceivedEvent {
|
|
|
99
99
|
uint descriptor,
|
|
100
100
|
uint batches
|
|
101
101
|
) internal view returns (Execution memory exec) {
|
|
102
|
-
bytes32 account;
|
|
103
|
-
bytes calldata state;
|
|
104
|
-
bytes calldata input;
|
|
105
|
-
(account, state, input) = unpackCommandContext(context);
|
|
102
|
+
(bytes32 account, bytes calldata state, bytes calldata input) = unpackCommandContext(context);
|
|
106
103
|
exec = Executions.open(state, input, descriptor, batches);
|
|
107
104
|
exec.account = account;
|
|
108
105
|
}
|
package/commands/Debit.sol
CHANGED
|
@@ -67,11 +67,8 @@ abstract contract DebitAccountInternal is DebitAccount {
|
|
|
67
67
|
) internal returns (bytes memory, bytes memory) {
|
|
68
68
|
if (value != 0) revert ValueNotAllowed();
|
|
69
69
|
if (state.length != 0) revert Executions.ZeroStride();
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
Cur memory cur = Decoders.wrap(input);
|
|
73
|
-
uint count = input.length / Sizes.Amount;
|
|
74
|
-
bytes memory output = new bytes(count * Sizes.Balance);
|
|
70
|
+
Cur memory cur = Decoders.open(input);
|
|
71
|
+
bytes memory output = new bytes(input.length);
|
|
75
72
|
uint i;
|
|
76
73
|
|
|
77
74
|
while (cur.more()) {
|
package/commands/admin/Base.sol
CHANGED
|
@@ -13,10 +13,7 @@ abstract contract AdminBase is NodeAccess, CommandBase {
|
|
|
13
13
|
uint descriptor,
|
|
14
14
|
uint batches
|
|
15
15
|
) internal view returns (Execution memory exec) {
|
|
16
|
-
bytes32 account;
|
|
17
|
-
bytes calldata state;
|
|
18
|
-
bytes calldata input;
|
|
19
|
-
(account, state, input) = unpackCommandContext(context);
|
|
16
|
+
(bytes32 account, bytes calldata state, bytes calldata input) = unpackCommandContext(context);
|
|
20
17
|
enforceAdmin(account, msg.sender);
|
|
21
18
|
exec = Executions.open(state, input, descriptor, batches);
|
|
22
19
|
exec.account = account;
|
package/core/Pipeline.sol
CHANGED
|
@@ -3,15 +3,25 @@ pragma solidity ^0.8.33;
|
|
|
3
3
|
|
|
4
4
|
import {Decoders, Cur, Readers, Reader} from "../Codec.sol";
|
|
5
5
|
import {PostHook} from "./Settlement.sol";
|
|
6
|
-
import {
|
|
6
|
+
import {Budgets} from "../execution/Budget.sol";
|
|
7
7
|
|
|
8
8
|
using Decoders for Cur;
|
|
9
9
|
using Readers for Reader;
|
|
10
|
-
|
|
10
|
+
|
|
11
|
+
/// @notice Hook implemented by hosts that execute encoded step streams.
|
|
12
|
+
abstract contract PipeHook {
|
|
13
|
+
/// @notice Execute a step stream and return its remaining native-value budget.
|
|
14
|
+
function pipe(
|
|
15
|
+
bytes32 account,
|
|
16
|
+
bytes memory state,
|
|
17
|
+
bytes calldata steps,
|
|
18
|
+
uint budget
|
|
19
|
+
) internal virtual returns (uint remaining);
|
|
20
|
+
}
|
|
11
21
|
|
|
12
22
|
/// @title Pipeline
|
|
13
23
|
/// @notice Core pipeline functionality shared by higher-level surfaces.
|
|
14
|
-
abstract contract Pipeline is PostHook {
|
|
24
|
+
abstract contract Pipeline is PipeHook, PostHook {
|
|
15
25
|
/// @dev Thrown when the pipeline finishes with non-empty threaded state.
|
|
16
26
|
error UnexpectedState();
|
|
17
27
|
|
|
@@ -36,18 +46,26 @@ abstract contract Pipeline is PostHook {
|
|
|
36
46
|
|
|
37
47
|
/// @notice Execute a STEP block stream through the pipeline.
|
|
38
48
|
/// @dev Reverts with `UnexpectedState` if the final threaded state is non-empty.
|
|
39
|
-
/// Callers remain responsible for settling
|
|
49
|
+
/// Callers remain responsible for settling the returned unspent value.
|
|
40
50
|
/// @param account Account identifier used for each dispatched step.
|
|
41
51
|
/// @param state Initial state block stream passed to the first step.
|
|
42
52
|
/// @param steps STEP block stream to execute.
|
|
43
|
-
/// @param budget
|
|
44
|
-
|
|
53
|
+
/// @param budget Native-value budget shared across all steps.
|
|
54
|
+
/// @return remaining Native value remaining after every step executes.
|
|
55
|
+
function pipe(
|
|
56
|
+
bytes32 account,
|
|
57
|
+
bytes memory state,
|
|
58
|
+
bytes calldata steps,
|
|
59
|
+
uint budget
|
|
60
|
+
) internal virtual override returns (uint remaining) {
|
|
45
61
|
Cur memory cur = Decoders.open(steps);
|
|
46
62
|
|
|
47
63
|
while (cur.more()) {
|
|
48
64
|
(uint cmd, uint resources, bytes calldata input) = cur.unpackStep();
|
|
65
|
+
uint128 value;
|
|
66
|
+
(budget, value) = Budgets.useResourceValue(budget, resources);
|
|
49
67
|
Reader memory txs;
|
|
50
|
-
(state, txs.source) = dispatch(cmd, account, state, input,
|
|
68
|
+
(state, txs.source) = dispatch(cmd, account, state, input, value);
|
|
51
69
|
|
|
52
70
|
while (txs.more()) {
|
|
53
71
|
(bytes32 from, bytes32 to, bytes32 asset, uint amount) = txs.unpackTransaction();
|
|
@@ -56,5 +74,6 @@ abstract contract Pipeline is PostHook {
|
|
|
56
74
|
}
|
|
57
75
|
|
|
58
76
|
if (state.length != 0) revert UnexpectedState();
|
|
77
|
+
return budget;
|
|
59
78
|
}
|
|
60
79
|
}
|
package/execution/Budget.sol
CHANGED
|
@@ -29,6 +29,15 @@ library Budgets {
|
|
|
29
29
|
return value;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/// @notice Deduct an exact native value from a scalar budget.
|
|
33
|
+
/// @param budget Remaining native value in wei.
|
|
34
|
+
/// @param value Native value to consume in wei.
|
|
35
|
+
/// @return remaining Native value remaining after the deduction.
|
|
36
|
+
function useValue(uint budget, uint value) internal pure returns (uint remaining) {
|
|
37
|
+
if (value > budget) revert InsufficientValue();
|
|
38
|
+
remaining = budget - value;
|
|
39
|
+
}
|
|
40
|
+
|
|
32
41
|
/// @notice Deduct the EVM value lane of `resources` from `budget`.
|
|
33
42
|
/// @dev EVM resources use the low 128 bits as native value/endowment.
|
|
34
43
|
/// @param budget Mutable budget to debit.
|
|
@@ -38,6 +47,19 @@ library Budgets {
|
|
|
38
47
|
return uint128(useValue(budget, uint128(resources)));
|
|
39
48
|
}
|
|
40
49
|
|
|
50
|
+
/// @notice Deduct the EVM value lane of `resources` from a scalar budget.
|
|
51
|
+
/// @param budget Remaining native value in wei.
|
|
52
|
+
/// @param resources Packed resources whose low 128 bits contain native value.
|
|
53
|
+
/// @return remaining Native value remaining after the deduction.
|
|
54
|
+
/// @return value Native value consumed from the budget.
|
|
55
|
+
function useResourceValue(
|
|
56
|
+
uint budget,
|
|
57
|
+
uint resources
|
|
58
|
+
) internal pure returns (uint remaining, uint128 value) {
|
|
59
|
+
value = uint128(resources);
|
|
60
|
+
remaining = useValue(budget, value);
|
|
61
|
+
}
|
|
62
|
+
|
|
41
63
|
/// @notice Remove and return all remaining value from `budget`.
|
|
42
64
|
/// @param budget Mutable budget to drain.
|
|
43
65
|
/// @return value Native value removed from the budget.
|
package/execution/Execution.sol
CHANGED
|
@@ -246,6 +246,20 @@ library Executions {
|
|
|
246
246
|
exec.decoders = cur.seekAbs(end);
|
|
247
247
|
}
|
|
248
248
|
|
|
249
|
+
/// @notice Validate a known key and consume the next block from an execution decoder lane.
|
|
250
|
+
/// @dev Validates no payload-size constraint beyond proving the complete block
|
|
251
|
+
/// lies within the selected lane's logical region.
|
|
252
|
+
/// @param exec Execution whose selected decoder cursor is advanced over the complete block.
|
|
253
|
+
/// @param lane Execution decoder lane to select.
|
|
254
|
+
/// @param key Expected block key.
|
|
255
|
+
/// @return abs Absolute position of the first payload byte.
|
|
256
|
+
/// @return end Absolute position immediately after the payload.
|
|
257
|
+
function consume(Execution memory exec, uint8 lane, bytes4 key) internal pure returns (uint abs, uint end) {
|
|
258
|
+
uint cur = exec.decoders.select(lane);
|
|
259
|
+
(abs, end) = Blocks.expectKey(cur.absolute(), key);
|
|
260
|
+
exec.decoders = cur.seekAbs(end);
|
|
261
|
+
}
|
|
262
|
+
|
|
249
263
|
/// @notice Validate and consume one block from a decoder lane, returning its complete encoding.
|
|
250
264
|
/// @param exec Execution whose selected decoder cursor is advanced past the block.
|
|
251
265
|
/// @param lane Execution decoder lane to select.
|
|
@@ -256,7 +270,7 @@ library Executions {
|
|
|
256
270
|
uint8 lane,
|
|
257
271
|
bytes4 key
|
|
258
272
|
) internal pure returns (bytes calldata data) {
|
|
259
|
-
(uint abs, uint end) = consume(exec, lane,
|
|
273
|
+
(uint abs, uint end) = consume(exec, lane, key);
|
|
260
274
|
data = msg.data[abs - Sizes.Header:end];
|
|
261
275
|
}
|
|
262
276
|
|
|
@@ -1403,13 +1417,20 @@ library Executions {
|
|
|
1403
1417
|
// Value and transaction writing
|
|
1404
1418
|
// -------------------------------------------------------------------------
|
|
1405
1419
|
|
|
1420
|
+
/// @notice Remove and return the remaining execution value budget.
|
|
1421
|
+
/// @param exec Execution whose budget is drained.
|
|
1422
|
+
/// @return budget Native value removed from the execution.
|
|
1423
|
+
function drainBudget(Execution memory exec) internal pure returns (uint budget) {
|
|
1424
|
+
budget = exec.budget;
|
|
1425
|
+
exec.budget = 0;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1406
1428
|
/// @notice Transfer the remaining value budget out of an execution.
|
|
1407
1429
|
/// @dev Clears `exec.budget` so the returned budget becomes its sole owner.
|
|
1408
1430
|
/// @param exec Execution whose budget is detached.
|
|
1409
1431
|
/// @return budget Detached budget containing the remaining value.
|
|
1410
1432
|
function takeBudget(Execution memory exec) internal pure returns (Budget memory budget) {
|
|
1411
|
-
budget.remaining = exec
|
|
1412
|
-
exec.budget = 0;
|
|
1433
|
+
budget.remaining = drainBudget(exec);
|
|
1413
1434
|
}
|
|
1414
1435
|
|
|
1415
1436
|
/// @notice Deduct an exact native value from the execution budget.
|
package/package.json
CHANGED
package/ports/Pipe.sol
CHANGED
|
@@ -3,17 +3,16 @@ pragma solidity ^0.8.33;
|
|
|
3
3
|
|
|
4
4
|
import {PortBase} from "./Base.sol";
|
|
5
5
|
import {Flags} from "../codec/Descriptors.sol";
|
|
6
|
-
import {
|
|
6
|
+
import {PipeHook} from "../core/Pipeline.sol";
|
|
7
7
|
import {Specs} from "../Codec.sol";
|
|
8
8
|
import {Execution, Executions, Lanes} from "../execution/Execution.sol";
|
|
9
|
-
import {Budget} from "../execution/Budget.sol";
|
|
10
9
|
|
|
11
10
|
using Executions for Execution;
|
|
12
11
|
|
|
13
12
|
/// @title PipePayablePort
|
|
14
13
|
/// @notice Port that consumes CONTEXT blocks and executes each input as a step stream.
|
|
15
14
|
/// Each context's input bytes are passed to the shared pipeline.
|
|
16
|
-
abstract contract PipePayablePort is PortBase,
|
|
15
|
+
abstract contract PipePayablePort is PortBase, PipeHook {
|
|
17
16
|
uint private immutable descriptor;
|
|
18
17
|
|
|
19
18
|
constructor() {
|
|
@@ -27,11 +26,11 @@ abstract contract PipePayablePort is PortBase, Pipeline {
|
|
|
27
26
|
/// @return Empty response bytes.
|
|
28
27
|
function portPipePayable(bytes calldata data) external payable onlyPeer returns (bytes memory) {
|
|
29
28
|
Execution memory exec = openInput(data, descriptor, 0);
|
|
30
|
-
|
|
29
|
+
uint budget = exec.drainBudget();
|
|
31
30
|
|
|
32
31
|
while (exec.more()) {
|
|
33
32
|
(bytes32 account, bytes calldata state, bytes calldata input) = exec.unpackContext(Lanes.Input);
|
|
34
|
-
pipe(account, state, input, budget);
|
|
33
|
+
budget = pipe(account, state, input, budget);
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
return "";
|