@rootzero/contracts 1.21.0 → 1.22.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 +57 -0
- package/README.md +12 -8
- package/annotations/Schema.sol +35 -2
- package/codec/Blocks.sol +64 -2
- package/codec/Decoders.sol +62 -8
- package/codec/Readers.sol +39 -0
- package/codec/Schema.sol +44 -35
- package/codec/Writers.sol +8 -0
- package/commands/Relay.sol +2 -1
- package/core/Host.sol +7 -10
- package/events/Dispatch.sol +1 -1
- package/events/Relay.sol +1 -1
- package/events/Route.sol +1 -1
- package/execution/Execution.sol +82 -4
- package/package.json +2 -3
- package/ports/Dispatch.sol +2 -1
- package/docs/Schema.md +0 -427
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,63 @@
|
|
|
3
3
|
Until the protocol reaches integration-stable status, minor versions may include
|
|
4
4
|
breaking API changes. Breaking changes are called out explicitly.
|
|
5
5
|
|
|
6
|
+
## 1.22.0
|
|
7
|
+
|
|
8
|
+
### Breaking Changes
|
|
9
|
+
|
|
10
|
+
- Declared child blocks are now structurally present whenever their parent is
|
|
11
|
+
non-empty. A child without a value uses its zero-payload block form instead
|
|
12
|
+
of being omitted. The `maybe` modifier now hints that onchain code accepts
|
|
13
|
+
that empty form; it no longer describes an absent item. Lists likewise keep
|
|
14
|
+
their header and represent no items with an empty payload.
|
|
15
|
+
- Removed `Schemas.Unit`. Use the empty form of the block key that carries the
|
|
16
|
+
relevant semantic meaning instead of a generic unit marker.
|
|
17
|
+
- Renamed `Decoders.take(cur, key)` to `takeBlock(cur, key)`. The `take` name is
|
|
18
|
+
now used by raw cursor navigation and returns the absolute start of a
|
|
19
|
+
bounds-checked byte range while advancing the cursor.
|
|
20
|
+
- Normalized standard schema bodies by removing their presentation-only outer
|
|
21
|
+
braces, and renamed the standard node field from `id` to `node`. Published
|
|
22
|
+
schema annotation bytes therefore change even though their wire layouts do
|
|
23
|
+
not.
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- Added empty-block inspection, conditional consumption, encoding, writing,
|
|
28
|
+
and execution-output helpers across `Blocks`, `Decoders`, `Readers`,
|
|
29
|
+
`Writers`, and `Executions`.
|
|
30
|
+
- Added `absolute`, `advance`, and raw `take` cursor helpers, plus `enter`
|
|
31
|
+
overloads that advance over a validated fixed payload prefix in one cursor
|
|
32
|
+
update.
|
|
33
|
+
- Added unnamed `schema` overloads for context-local specifications and an
|
|
34
|
+
unnamed `Blocks.schema` factory overload.
|
|
35
|
+
- Added the offchain-only `at N` schema projection hint. Explicit positions are
|
|
36
|
+
reserved first, then unannotated siblings fill the remaining positions in
|
|
37
|
+
declaration order; wire encoding and onchain decoding remain unchanged.
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
|
|
41
|
+
- Clarified that one optional pair of outer braces is presentation-only for all
|
|
42
|
+
non-empty schema bodies, including custom top-level `many` schemas.
|
|
43
|
+
- Updated custom input examples to group fixed-width fields for direct calldata
|
|
44
|
+
reads, demonstrate empty child blocks, and separate top-level and nested swap
|
|
45
|
+
decoding.
|
|
46
|
+
- Excluded repository documentation and examples from the prepared npm package;
|
|
47
|
+
the package continues to contain Solidity sources, the README, changelog, and
|
|
48
|
+
license.
|
|
49
|
+
|
|
50
|
+
### Upgrade Compatibility
|
|
51
|
+
|
|
52
|
+
- Emit every declared child header in schema order. When a `maybe` child has no
|
|
53
|
+
value, emit that child's key with a zero payload length and call
|
|
54
|
+
`tryConsumeEmpty` before its strict semantic unpacker.
|
|
55
|
+
- Replace `Schemas.Unit` markers with an empty block carrying the key expected
|
|
56
|
+
by the receiving schema.
|
|
57
|
+
- Replace `cur.take(key)` with `cur.takeBlock(key)`. Use `cur.take(amount)` only
|
|
58
|
+
for raw fixed-width ranges.
|
|
59
|
+
- Indexers should accept braced and unbraced schema bodies, treat `maybe` as an
|
|
60
|
+
empty-value hint, and apply `at N` only after decoding declaration-order wire
|
|
61
|
+
data.
|
|
62
|
+
|
|
6
63
|
## 1.21.0
|
|
7
64
|
|
|
8
65
|
### Breaking Changes
|
package/README.md
CHANGED
|
@@ -114,16 +114,20 @@ identifiers inside, never how the bytes are laid out.
|
|
|
114
114
|
|
|
115
115
|
Schemas can express more than flat fields: a block may contain any number of
|
|
116
116
|
nested child blocks (`#bytes as payload` names raw dynamic bytes), items can be
|
|
117
|
-
marked `maybe`
|
|
118
|
-
paths give off-chain tooling presentation
|
|
119
|
-
on the wire.
|
|
120
|
-
|
|
117
|
+
marked `maybe` when their empty form is accepted or `many` when they form a
|
|
118
|
+
list, and aliases and dotted field paths give off-chain tooling presentation
|
|
119
|
+
names without changing a single byte on the wire. Declared child headers are
|
|
120
|
+
always present; a zero payload length represents an empty block. An `at N` hint
|
|
121
|
+
can reposition one field in off-chain presentation without changing its wire
|
|
122
|
+
position. The full schema language is specified in
|
|
123
|
+
[`docs/Schema.md`](https://github.com/lastqubit/rootzero-evm/blob/main/docs/Schema.md). The standard block schemas live in
|
|
121
124
|
`Schemas` and their runtime keys in `Keys` (both via
|
|
122
125
|
`@rootzero/contracts/Codec.sol`).
|
|
123
126
|
|
|
124
|
-
A rare top-level list is published as a custom schema
|
|
125
|
-
Its context-local schema key becomes
|
|
126
|
-
|
|
127
|
+
A rare top-level list is published as a custom schema consisting of one item,
|
|
128
|
+
such as `many #asset` or `{ many #asset }`. Its context-local schema key becomes
|
|
129
|
+
the outer block key accepted by the endpoint; a list alongside sibling items
|
|
130
|
+
continues to use the generic `#list` key.
|
|
127
131
|
|
|
128
132
|
## Batches
|
|
129
133
|
|
|
@@ -492,7 +496,7 @@ Repo layout:
|
|
|
492
496
|
- `contracts/blocks` — block schema, cursor parsing, writers
|
|
493
497
|
- `contracts/utils` — ids, nodes, assets, accounts, layout, ECDSA
|
|
494
498
|
- `contracts/events` — event contracts and emitters
|
|
495
|
-
- `docs` — [`Schema.md`](docs/Schema.md) (wire format and schema DSL)
|
|
499
|
+
- `docs` — [`Schema.md`](https://github.com/lastqubit/rootzero-evm/blob/main/docs/Schema.md) (wire format and schema DSL)
|
|
496
500
|
|
|
497
501
|
Use this library to create a new rootzero host, implement a command, or reuse
|
|
498
502
|
the protocol's block format in tooling. It is the shared protocol foundation,
|
package/annotations/Schema.sol
CHANGED
|
@@ -11,6 +11,23 @@ import {Runtime} from "../core/Runtime.sol";
|
|
|
11
11
|
/// @dev Schema annotations accumulate for distinct block keys. For a trusted
|
|
12
12
|
/// emitter, the latest schema for the same block key replaces the earlier claim.
|
|
13
13
|
abstract contract Schema is Runtime, AnnotationEvent {
|
|
14
|
+
/// @notice Construct and publish an unnamed context-local block specification.
|
|
15
|
+
/// @param key Context-local key value.
|
|
16
|
+
/// @param min Minimum accepted payload length.
|
|
17
|
+
/// @param max Maximum accepted payload length; zero means unbounded.
|
|
18
|
+
/// @param hint Initial per-block payload capacity.
|
|
19
|
+
/// @param body Schema DSL string describing the block payload body.
|
|
20
|
+
/// @return spec The context-local block specification.
|
|
21
|
+
function schema(
|
|
22
|
+
uint32 key,
|
|
23
|
+
uint32 min,
|
|
24
|
+
uint32 max,
|
|
25
|
+
uint32 hint,
|
|
26
|
+
string memory body
|
|
27
|
+
) internal returns (uint spec) {
|
|
28
|
+
return schema(key, min, max, hint, body, bytes32(0));
|
|
29
|
+
}
|
|
30
|
+
|
|
14
31
|
/// @notice Construct and publish a context-local block specification.
|
|
15
32
|
/// @param key Context-local key value.
|
|
16
33
|
/// @param min Minimum accepted payload length.
|
|
@@ -27,8 +44,16 @@ abstract contract Schema is Runtime, AnnotationEvent {
|
|
|
27
44
|
string memory body,
|
|
28
45
|
bytes32 name
|
|
29
46
|
) internal returns (uint spec) {
|
|
30
|
-
|
|
31
|
-
|
|
47
|
+
return schema(Specs.create(key, min, max, hint), body, name);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/// @notice Construct and publish an unnamed exact-size context-local block specification.
|
|
51
|
+
/// @param key Context-local key value.
|
|
52
|
+
/// @param size Exact payload length and initial per-block payload capacity.
|
|
53
|
+
/// @param body Schema DSL string describing the block payload body.
|
|
54
|
+
/// @return spec The context-local block specification.
|
|
55
|
+
function schema(uint32 key, uint32 size, string memory body) internal returns (uint spec) {
|
|
56
|
+
return schema(key, size, body, bytes32(0));
|
|
32
57
|
}
|
|
33
58
|
|
|
34
59
|
/// @notice Construct and publish an exact-size context-local block specification.
|
|
@@ -41,6 +66,14 @@ abstract contract Schema is Runtime, AnnotationEvent {
|
|
|
41
66
|
return schema(Specs.create(key, size), body, name);
|
|
42
67
|
}
|
|
43
68
|
|
|
69
|
+
/// @notice Publish an unnamed, already constructed block specification for the current host.
|
|
70
|
+
/// @param spec Packed block specification.
|
|
71
|
+
/// @param body Schema DSL string describing the block payload body.
|
|
72
|
+
/// @return The published block specification.
|
|
73
|
+
function schema(uint spec, string memory body) internal returns (uint) {
|
|
74
|
+
return schema(spec, body, bytes32(0));
|
|
75
|
+
}
|
|
76
|
+
|
|
44
77
|
/// @notice Publish an already constructed block specification for the current host.
|
|
45
78
|
/// @param spec Packed block specification.
|
|
46
79
|
/// @param body Schema DSL string describing the block payload body.
|
package/codec/Blocks.sol
CHANGED
|
@@ -73,6 +73,17 @@ library Blocks {
|
|
|
73
73
|
len = uint32(head >> 192);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
/// @notice Decode a complete block header within an absolute calldata region.
|
|
77
|
+
/// @param abs Absolute position of the header.
|
|
78
|
+
/// @param end Absolute region boundary.
|
|
79
|
+
/// @return key Decoded block key.
|
|
80
|
+
/// @return len Decoded payload length.
|
|
81
|
+
function peek(uint abs, uint end) internal pure returns (bytes4 key, uint len) {
|
|
82
|
+
if (abs > end || Sizes.Header > end - abs) revert MalformedBlocks();
|
|
83
|
+
(key, len) = header(abs);
|
|
84
|
+
if (len > end - abs - Sizes.Header) revert MalformedBlocks();
|
|
85
|
+
}
|
|
86
|
+
|
|
76
87
|
/// @notice Validate a block header at an absolute calldata position.
|
|
77
88
|
/// @dev DANGER: This performs an unchecked calldata read and does not ensure `end`
|
|
78
89
|
/// lies within the caller's logical calldata region. Only the key, minimum,
|
|
@@ -101,6 +112,17 @@ library Blocks {
|
|
|
101
112
|
end = body + size;
|
|
102
113
|
}
|
|
103
114
|
|
|
115
|
+
/// @notice Validate an empty block at an absolute calldata position.
|
|
116
|
+
/// @dev DANGER: This performs an unchecked calldata read. The caller must
|
|
117
|
+
/// validate the returned end against its logical calldata region.
|
|
118
|
+
/// @param abs Absolute position of the block header.
|
|
119
|
+
/// @param key Expected block key.
|
|
120
|
+
/// @return end Absolute position immediately after the empty block header.
|
|
121
|
+
function expectEmpty(uint abs, bytes4 key) internal pure returns (uint end) {
|
|
122
|
+
if (header(abs, key) != 0) revert InvalidBlock();
|
|
123
|
+
return abs + Sizes.Header;
|
|
124
|
+
}
|
|
125
|
+
|
|
104
126
|
/// @notice Return whether `abs` identifies a header with `key` before an absolute end.
|
|
105
127
|
/// @param abs Absolute calldata position to inspect.
|
|
106
128
|
/// @param end Absolute region boundary.
|
|
@@ -111,6 +133,16 @@ library Blocks {
|
|
|
111
133
|
return bytes4(read32(abs)) == key;
|
|
112
134
|
}
|
|
113
135
|
|
|
136
|
+
/// @notice Return whether `abs` identifies a complete empty block header.
|
|
137
|
+
/// @param abs Absolute position to inspect.
|
|
138
|
+
/// @param end Absolute region boundary.
|
|
139
|
+
/// @param key Expected block key.
|
|
140
|
+
/// @return Whether the expected key occurs with a zero-length payload.
|
|
141
|
+
function isEmpty(uint abs, uint end, bytes4 key) internal pure returns (bool) {
|
|
142
|
+
if (!hasAt(abs, end, key)) return false;
|
|
143
|
+
return uint32(uint(read32(abs)) >> 192) == 0;
|
|
144
|
+
}
|
|
145
|
+
|
|
114
146
|
/// @notice Find the first block with `key` at or after absolute position `abs`.
|
|
115
147
|
/// @param abs Absolute search position.
|
|
116
148
|
/// @param end Absolute region boundary.
|
|
@@ -160,6 +192,18 @@ library Blocks {
|
|
|
160
192
|
|
|
161
193
|
// Generic block writes
|
|
162
194
|
|
|
195
|
+
/// @notice Write an empty block header at `i`.
|
|
196
|
+
/// @dev DANGER: Unchecked memory write. Reserve `Sizes.Header` bytes first.
|
|
197
|
+
/// @param dst Destination buffer.
|
|
198
|
+
/// @param i Relative write position.
|
|
199
|
+
/// @param key Block key.
|
|
200
|
+
function writeEmpty(bytes memory dst, uint i, bytes4 key) internal pure {
|
|
201
|
+
uint head = uint(uint32(key)) << 224;
|
|
202
|
+
assembly ("memory-safe") {
|
|
203
|
+
mstore(add(add(dst, 0x20), i), head)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
163
207
|
/// @notice Write a custom block with one payload word at `i`.
|
|
164
208
|
/// @dev DANGER: Unchecked memory write. Reserve `Sizes.B32` bytes first.
|
|
165
209
|
/// @param dst Destination buffer.
|
|
@@ -1729,6 +1773,14 @@ library Blocks {
|
|
|
1729
1773
|
|
|
1730
1774
|
// Generic factories
|
|
1731
1775
|
|
|
1776
|
+
/// @notice Encode an empty block.
|
|
1777
|
+
/// @param key Block type key.
|
|
1778
|
+
/// @return value Encoded empty block header.
|
|
1779
|
+
function empty(bytes4 key) internal pure returns (bytes memory value) {
|
|
1780
|
+
value = allocate(Sizes.Header);
|
|
1781
|
+
writeEmpty(value, 0, key);
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1732
1784
|
/// @notice Encode a block with a raw payload.
|
|
1733
1785
|
/// @param key Block type key.
|
|
1734
1786
|
/// @param payload Raw payload bytes.
|
|
@@ -1831,6 +1883,14 @@ library Blocks {
|
|
|
1831
1883
|
/// @notice Encode a SCHEMA block.
|
|
1832
1884
|
/// @param spec Block specification.
|
|
1833
1885
|
/// @param body Schema body.
|
|
1886
|
+
/// @return value Encoded SCHEMA block bytes.
|
|
1887
|
+
function schema(uint spec, string memory body) internal pure returns (bytes memory value) {
|
|
1888
|
+
return schema(spec, body, bytes32(0));
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
/// @notice Encode a named SCHEMA block.
|
|
1892
|
+
/// @param spec Block specification.
|
|
1893
|
+
/// @param body Schema body.
|
|
1834
1894
|
/// @param name Schema name.
|
|
1835
1895
|
/// @return value Encoded SCHEMA block bytes.
|
|
1836
1896
|
function schema(uint spec, string memory body, bytes32 name) internal pure returns (bytes memory value) {
|
|
@@ -1931,7 +1991,8 @@ library Blocks {
|
|
|
1931
1991
|
}
|
|
1932
1992
|
|
|
1933
1993
|
/// @notice Encode a RELAY block.
|
|
1934
|
-
/// @param portal Destination portal
|
|
1994
|
+
/// @param portal Destination portal implementation's host ID, passed through
|
|
1995
|
+
/// without semantic validation.
|
|
1935
1996
|
/// @param resources Chain-specific resources for the destination context.
|
|
1936
1997
|
/// @param input Nested input block stream.
|
|
1937
1998
|
/// @return value Encoded RELAY block bytes.
|
|
@@ -1949,7 +2010,8 @@ library Blocks {
|
|
|
1949
2010
|
}
|
|
1950
2011
|
|
|
1951
2012
|
/// @notice Encode a DISPATCH block.
|
|
1952
|
-
/// @param portal Destination portal
|
|
2013
|
+
/// @param portal Destination portal implementation's host ID, passed through
|
|
2014
|
+
/// without semantic validation.
|
|
1953
2015
|
/// @param resources Chain-specific resources for the destination dispatch.
|
|
1954
2016
|
/// @param payload Encoded payload.
|
|
1955
2017
|
/// @return value Encoded DISPATCH block bytes.
|
package/codec/Decoders.sol
CHANGED
|
@@ -52,6 +52,13 @@ library Decoders {
|
|
|
52
52
|
return cur.state.more();
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/// @notice Return the cursor's current absolute calldata position.
|
|
56
|
+
/// @param cur Cursor to inspect.
|
|
57
|
+
/// @return Current absolute calldata position.
|
|
58
|
+
function absolute(Cur memory cur) internal pure returns (uint) {
|
|
59
|
+
return cur.state.absolute();
|
|
60
|
+
}
|
|
61
|
+
|
|
55
62
|
/// @notice Validate and consume the next block from a cursor.
|
|
56
63
|
/// @param cur Cursor advanced over the complete block.
|
|
57
64
|
/// @param spec Expected block specification.
|
|
@@ -62,6 +69,18 @@ library Decoders {
|
|
|
62
69
|
cur.state = cur.state.seekAbs(end);
|
|
63
70
|
}
|
|
64
71
|
|
|
72
|
+
/// @notice Consume a matching empty block from a cursor when present.
|
|
73
|
+
/// @param cur Cursor advanced only when the matching block is empty.
|
|
74
|
+
/// @param key Expected block key.
|
|
75
|
+
/// @return Whether an empty block was consumed.
|
|
76
|
+
function tryConsumeEmpty(Cur memory cur, bytes4 key) internal pure returns (bool) {
|
|
77
|
+
(uint i, uint offset, uint size) = cur.state.decode();
|
|
78
|
+
(bytes4 current, uint len) = Blocks.peek(offset + i, offset + size);
|
|
79
|
+
if (current != key || len != 0) return false;
|
|
80
|
+
cur.state = cur.state.seek(i + Sizes.Header);
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
|
|
65
84
|
/// @notice Validate and enter the payload of the next block in a cursor.
|
|
66
85
|
/// @dev The cursor remains in its existing frame so callers can decode child
|
|
67
86
|
/// blocks in place. Callers should prove complete payload consumption with
|
|
@@ -71,8 +90,38 @@ library Decoders {
|
|
|
71
90
|
/// @return abs Absolute position of the first payload byte.
|
|
72
91
|
/// @return end Absolute position immediately after the payload.
|
|
73
92
|
function enter(Cur memory cur, uint spec) internal pure returns (uint abs, uint end) {
|
|
93
|
+
return enter(cur, spec, 0);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/// @notice Validate a parent block and advance over a fixed payload prefix.
|
|
97
|
+
/// @dev `amount` is relative to the payload start and cannot exceed the
|
|
98
|
+
/// current parent payload. The returned `abs` remains the payload start.
|
|
99
|
+
/// @param cur Cursor advanced over the block header and fixed prefix.
|
|
100
|
+
/// @param spec Expected parent block specification.
|
|
101
|
+
/// @param amount Number of initial payload bytes to advance over.
|
|
102
|
+
/// @return abs Absolute position of the first payload byte.
|
|
103
|
+
/// @return end Absolute position immediately after the payload.
|
|
104
|
+
function enter(Cur memory cur, uint spec, uint amount) internal pure returns (uint abs, uint end) {
|
|
74
105
|
(abs, end) = Blocks.expect(cur.state.absolute(), spec);
|
|
75
|
-
|
|
106
|
+
if (amount > end - abs) revert Blocks.InvalidBlock();
|
|
107
|
+
cur.state = cur.state.seekAbs(abs + amount);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/// @notice Advance a cursor by a raw byte count.
|
|
111
|
+
/// @dev No block header or schema is validated.
|
|
112
|
+
/// @param cur Cursor advanced by `amount` bytes.
|
|
113
|
+
/// @param amount Number of bytes to advance.
|
|
114
|
+
function advance(Cur memory cur, uint amount) internal pure {
|
|
115
|
+
cur.state = cur.state.advance(amount);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/// @notice Take a raw byte range from the cursor.
|
|
119
|
+
/// @dev No block header or schema is validated.
|
|
120
|
+
/// @param cur Cursor advanced by `amount` bytes.
|
|
121
|
+
/// @param amount Number of bytes to take.
|
|
122
|
+
/// @return abs Absolute position of the first taken byte.
|
|
123
|
+
function take(Cur memory cur, uint amount) internal pure returns (uint abs) {
|
|
124
|
+
(cur.state, abs) = cur.state.consume(amount);
|
|
76
125
|
}
|
|
77
126
|
|
|
78
127
|
/// @notice Require a decoder cursor to be at absolute position `abs`.
|
|
@@ -128,9 +177,7 @@ library Decoders {
|
|
|
128
177
|
/// @return len Payload length.
|
|
129
178
|
function peek(Cur memory cur, uint i) internal pure returns (bytes4 key, uint len) {
|
|
130
179
|
(, uint offset, uint size) = cur.state.decode();
|
|
131
|
-
|
|
132
|
-
(key, len) = Blocks.header(offset + i);
|
|
133
|
-
if (len > size - i - Sizes.Header) revert Blocks.MalformedBlocks();
|
|
180
|
+
return Blocks.peek(offset + i, offset + size);
|
|
134
181
|
}
|
|
135
182
|
|
|
136
183
|
/// @notice Return the relative position immediately after the current block.
|
|
@@ -161,6 +208,15 @@ library Decoders {
|
|
|
161
208
|
return Blocks.hasAt(offset + i, offset + len, key);
|
|
162
209
|
}
|
|
163
210
|
|
|
211
|
+
/// @notice Return whether the current block has `key` and an empty payload.
|
|
212
|
+
/// @param cur Cursor positioned at a block.
|
|
213
|
+
/// @param key Expected block key.
|
|
214
|
+
/// @return Whether a complete matching empty block header occurs at the current position.
|
|
215
|
+
function isEmpty(Cur memory cur, bytes4 key) internal pure returns (bool) {
|
|
216
|
+
(uint i, uint offset, uint len) = cur.state.decode();
|
|
217
|
+
return Blocks.isEmpty(offset + i, offset + len, key);
|
|
218
|
+
}
|
|
219
|
+
|
|
164
220
|
/// @notice Find `key` at or after relative position `i`.
|
|
165
221
|
/// @param cur Cursor to search.
|
|
166
222
|
/// @param i Relative search position.
|
|
@@ -210,7 +266,7 @@ library Decoders {
|
|
|
210
266
|
/// @param cur Cursor advanced past the block.
|
|
211
267
|
/// @param key Expected block key.
|
|
212
268
|
/// @return out Cursor spanning the complete encoded block.
|
|
213
|
-
function
|
|
269
|
+
function takeBlock(Cur memory cur, bytes4 key) internal pure returns (Cur memory out) {
|
|
214
270
|
uint abs = cur.state.absolute();
|
|
215
271
|
(, uint end) = consume(cur, Specs.create(key, 0, 0, 0));
|
|
216
272
|
out.state = Cursors.create(abs, end - abs, 0, 0, 0);
|
|
@@ -256,9 +312,7 @@ library Decoders {
|
|
|
256
312
|
|
|
257
313
|
/// @dev Return the next raw calldata word and advance by `size` bytes.
|
|
258
314
|
function next(Cur memory cur, uint size) private pure returns (bytes32 value) {
|
|
259
|
-
|
|
260
|
-
(cur.state, abs) = cur.state.consume(size);
|
|
261
|
-
value = Blocks.read32(abs);
|
|
315
|
+
value = Blocks.read32(take(cur, size));
|
|
262
316
|
}
|
|
263
317
|
|
|
264
318
|
/// @notice Return the next raw byte and advance the cursor by one byte.
|
package/codec/Readers.sol
CHANGED
|
@@ -45,6 +45,45 @@ library Readers {
|
|
|
45
45
|
return cur.i != cur.source.length;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
/// @notice Return whether the current block has `key` and an empty payload.
|
|
49
|
+
/// @param cur Reader inspected without advancing.
|
|
50
|
+
/// @param key Expected block key.
|
|
51
|
+
/// @return Whether a complete matching empty block header occurs at the current position.
|
|
52
|
+
function isEmpty(Reader memory cur, bytes4 key) internal pure returns (bool) {
|
|
53
|
+
bytes memory source = cur.source;
|
|
54
|
+
uint i = cur.i;
|
|
55
|
+
if (i > source.length || source.length - i < Sizes.Header) return false;
|
|
56
|
+
|
|
57
|
+
uint header;
|
|
58
|
+
assembly ("memory-safe") {
|
|
59
|
+
header := mload(add(add(source, 0x20), i))
|
|
60
|
+
}
|
|
61
|
+
return bytes4(uint32(header >> 224)) == key && uint32(header >> 192) == 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// @notice Consume a matching empty block from the reader when present.
|
|
65
|
+
/// @param cur Reader advanced only when the matching block is empty.
|
|
66
|
+
/// @param key Expected block key.
|
|
67
|
+
/// @return Whether an empty block was consumed.
|
|
68
|
+
function tryConsumeEmpty(Reader memory cur, bytes4 key) internal pure returns (bool) {
|
|
69
|
+
bytes memory source = cur.source;
|
|
70
|
+
uint i = cur.i;
|
|
71
|
+
if (i > source.length || source.length - i < Sizes.Header) revert InvalidBlock();
|
|
72
|
+
|
|
73
|
+
bytes4 current;
|
|
74
|
+
uint len;
|
|
75
|
+
assembly ("memory-safe") {
|
|
76
|
+
let header := mload(add(add(source, 0x20), i))
|
|
77
|
+
current := header
|
|
78
|
+
len := and(shr(192, header), 0xffffffff)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (len > source.length - i - Sizes.Header) revert InvalidBlock();
|
|
82
|
+
if (current != key || len != 0) return false;
|
|
83
|
+
cur.i += Sizes.Header;
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
|
|
48
87
|
/// @notice Validate and consume the current block, advancing `cur.i` past it.
|
|
49
88
|
/// @param cur Reader to advance.
|
|
50
89
|
/// @param key Expected block key.
|
package/codec/Schema.sol
CHANGED
|
@@ -8,24 +8,34 @@ pragma solidity ^0.8.33;
|
|
|
8
8
|
//
|
|
9
9
|
// Schema:
|
|
10
10
|
// - block aliases are published separately from payload schemas
|
|
11
|
-
// - payload schemas are `""
|
|
11
|
+
// - payload schemas are `""` or a comma-separated item sequence; one optional
|
|
12
|
+
// pair of outer braces may wrap a non-empty sequence without changing meaning
|
|
12
13
|
// - an empty schema string means the block has no structured payload
|
|
13
14
|
// - commas separate siblings at every level
|
|
14
|
-
// - braces
|
|
15
|
+
// - braces are presentation-only and do not change payload layout
|
|
15
16
|
// - command inputs are a single run when the input schema is non-empty
|
|
16
17
|
// - command state is a single active state run without trailing globals
|
|
17
18
|
// - run items may repeat at top level for batching
|
|
18
|
-
// -
|
|
19
|
-
// -
|
|
20
|
-
// -
|
|
21
|
-
//
|
|
19
|
+
// - every declared child block header is present when its parent is non-empty
|
|
20
|
+
// - any block may use a zero-length payload as its empty form
|
|
21
|
+
// - `maybe #x` hints that the onchain consumer accepts the empty form of `#x`
|
|
22
|
+
// when emptiness is not already intrinsic to the referenced block type
|
|
23
|
+
// - `many #x` alongside other items emits one generic list block containing
|
|
24
|
+
// zero or more repeated `#x` items; the list header is always present
|
|
25
|
+
// - a custom schema consisting of exactly one `many #x` item uses its custom
|
|
26
|
+
// key for the outer list block and contains repeated `#x` items directly,
|
|
27
|
+
// whether or not the item is wrapped in braces
|
|
22
28
|
// - endpoint descriptor lanes identify their top-level block key directly
|
|
23
|
-
// - `portal` fields
|
|
29
|
+
// - `portal` fields identify destination portal hosts. By convention the value
|
|
30
|
+
// is the portal implementation's host ID; core passes it through unchanged
|
|
31
|
+
// and hooks may validate or resolve it for their transport
|
|
24
32
|
// - `resources` fields are chain-specific resource words. A portal adapter
|
|
25
33
|
// interprets them for the destination runtime. EVM resources use the low
|
|
26
34
|
// 128 bits as native value.
|
|
27
35
|
// - dotted field names and aliases, e.g. `dst.portal` or `#bytes as dst.payload`,
|
|
28
36
|
// are offchain projection metadata only and do not change runtime encoding
|
|
37
|
+
// - `at N` assigns an offchain presentation position to one sibling; explicit
|
|
38
|
+
// positions are reserved first and unannotated siblings retain relative order
|
|
29
39
|
// - child blocks resolve by alias in the active schema context; unresolved aliases are invalid
|
|
30
40
|
// - schema strings describe the payload body only; the `Block` event carries the alias
|
|
31
41
|
// - items are encoded in declaration order
|
|
@@ -63,7 +73,6 @@ pragma solidity ^0.8.33;
|
|
|
63
73
|
library Schemas {
|
|
64
74
|
// Empty and reserved payloads
|
|
65
75
|
|
|
66
|
-
string constant Unit = "";
|
|
67
76
|
string constant Bytes = "";
|
|
68
77
|
string constant String = "";
|
|
69
78
|
string constant List = "";
|
|
@@ -71,47 +80,47 @@ library Schemas {
|
|
|
71
80
|
|
|
72
81
|
// One-word payloads
|
|
73
82
|
|
|
74
|
-
string constant Node = "
|
|
75
|
-
string constant Account = "
|
|
76
|
-
string constant Asset = "
|
|
77
|
-
string constant Status = "
|
|
83
|
+
string constant Node = "uint node";
|
|
84
|
+
string constant Account = "bytes32 account";
|
|
85
|
+
string constant Asset = "bytes32 asset";
|
|
86
|
+
string constant Status = "uint code";
|
|
78
87
|
|
|
79
88
|
// Two-word payloads
|
|
80
89
|
|
|
81
|
-
string constant Amount = "
|
|
82
|
-
string constant Balance = "
|
|
83
|
-
string constant AccountAsset = "
|
|
84
|
-
string constant HostAsset = "
|
|
90
|
+
string constant Amount = "bytes32 asset, uint amount";
|
|
91
|
+
string constant Balance = "bytes32 asset, uint amount";
|
|
92
|
+
string constant AccountAsset = "bytes32 account, bytes32 asset";
|
|
93
|
+
string constant HostAsset = "uint host, bytes32 asset";
|
|
85
94
|
|
|
86
95
|
// Three-word payloads
|
|
87
96
|
|
|
88
|
-
string constant Allocation = "
|
|
89
|
-
string constant Allowance = "
|
|
90
|
-
string constant Custody = "
|
|
91
|
-
string constant AccountAmount = "
|
|
92
|
-
string constant HostAmount = "
|
|
93
|
-
string constant HostAccountAsset = "
|
|
97
|
+
string constant Allocation = "uint host, bytes32 asset, uint amount";
|
|
98
|
+
string constant Allowance = "uint host, bytes32 asset, uint amount";
|
|
99
|
+
string constant Custody = "uint host, bytes32 asset, uint amount";
|
|
100
|
+
string constant AccountAmount = "bytes32 account, bytes32 asset, uint amount";
|
|
101
|
+
string constant HostAmount = "uint host, bytes32 asset, uint amount";
|
|
102
|
+
string constant HostAccountAsset = "uint host, bytes32 account, bytes32 asset";
|
|
94
103
|
|
|
95
104
|
// Four-word payloads
|
|
96
105
|
|
|
97
|
-
string constant Position = "
|
|
98
|
-
string constant Transaction = "
|
|
99
|
-
string constant HostAccountAmount = "
|
|
106
|
+
string constant Position = "bytes32 asset, uint amount, bytes32 liability, uint debt";
|
|
107
|
+
string constant Transaction = "bytes32 from, bytes32 to, bytes32 asset, uint amount";
|
|
108
|
+
string constant HostAccountAmount = "uint host, bytes32 account, bytes32 asset, uint amount";
|
|
100
109
|
|
|
101
110
|
// Composite payloads
|
|
102
111
|
|
|
103
|
-
string constant Call = "
|
|
104
|
-
string constant Step = "
|
|
105
|
-
string constant Relay = "
|
|
106
|
-
string constant Dispatch = "
|
|
107
|
-
string constant Context = "
|
|
108
|
-
string constant Recover = "
|
|
109
|
-
string constant Annotation = "
|
|
112
|
+
string constant Call = "uint target, uint resources, #bytes as payload";
|
|
113
|
+
string constant Step = "uint cmd, uint resources, #bytes as input";
|
|
114
|
+
string constant Relay = "uint portal, uint resources, #bytes as input";
|
|
115
|
+
string constant Dispatch = "uint portal, uint resources, #bytes as payload";
|
|
116
|
+
string constant Context = "bytes32 account, #bytes as state, #bytes as input";
|
|
117
|
+
string constant Recover = "uint handler, uint resources, bytes32 key, #bytes as witness";
|
|
118
|
+
string constant Annotation = "uint entity, #bytes as data";
|
|
110
119
|
|
|
111
120
|
// Annotation payloads
|
|
112
121
|
|
|
113
|
-
string constant Action = "
|
|
114
|
-
string constant Label = "
|
|
115
|
-
string constant Schema = "
|
|
122
|
+
string constant Action = "uint action";
|
|
123
|
+
string constant Label = "bytes32 namespace, #string as name";
|
|
124
|
+
string constant Schema = "uint spec, #string as body, bytes32 name";
|
|
116
125
|
}
|
|
117
126
|
|
package/codec/Writers.sol
CHANGED
|
@@ -73,6 +73,14 @@ library Writers {
|
|
|
73
73
|
// Append helpers
|
|
74
74
|
// -------------------------------------------------------------------------
|
|
75
75
|
|
|
76
|
+
/// @notice Append an empty block.
|
|
77
|
+
/// @param writer Destination writer.
|
|
78
|
+
/// @param key Block key.
|
|
79
|
+
function appendEmpty(Writer memory writer, bytes4 key) internal pure {
|
|
80
|
+
uint i = reserve(writer, Sizes.Header);
|
|
81
|
+
Blocks.writeEmpty(writer.dst, i, key);
|
|
82
|
+
}
|
|
83
|
+
|
|
76
84
|
/// @notice Append arbitrary bytes to the writer.
|
|
77
85
|
/// @param writer Destination writer; `i` is advanced by `data.length`.
|
|
78
86
|
/// @param data Bytes to append.
|
package/commands/Relay.sol
CHANGED
|
@@ -8,7 +8,8 @@ using Executions for Execution;
|
|
|
8
8
|
/// @notice Hook implemented by hosts that relay command contexts.
|
|
9
9
|
abstract contract RelayPayableHook {
|
|
10
10
|
/// @notice Override to relay a command context to `portal`.
|
|
11
|
-
/// @param portal Destination portal
|
|
11
|
+
/// @param portal Destination portal implementation's host ID. Implementations
|
|
12
|
+
/// may validate or resolve it for their transport.
|
|
12
13
|
/// @param resources Chain-specific destination resources. EVM adapters
|
|
13
14
|
/// may interpret this as packed execution gas and destination value.
|
|
14
15
|
/// @param account Destination command account.
|
package/core/Host.sol
CHANGED
|
@@ -61,16 +61,19 @@ abstract contract CommandHost is CommanderAccess, CallerAccess, HostIntroduction
|
|
|
61
61
|
function enforceCaller(address caller) internal view virtual override returns (address) {
|
|
62
62
|
return enforceCommander(caller);
|
|
63
63
|
}
|
|
64
|
-
|
|
65
64
|
}
|
|
66
65
|
|
|
67
66
|
/// @title Admins
|
|
68
67
|
/// @notice Optional bundle of the default host administration commands.
|
|
69
|
-
abstract contract Admins is Annotate, ExecutePayable, Authorize, Unauthorize {
|
|
68
|
+
abstract contract Admins is Annotate, ExecutePayable, Authorize, Unauthorize {
|
|
69
|
+
|
|
70
|
+
}
|
|
70
71
|
|
|
71
72
|
/// @title Guardians
|
|
72
73
|
/// @notice Optional bundle for guardian management and the default revoke guard.
|
|
73
|
-
abstract contract Guardians is Appoint, Dismiss, Revoke {
|
|
74
|
+
abstract contract Guardians is Appoint, Dismiss, Revoke {
|
|
75
|
+
|
|
76
|
+
}
|
|
74
77
|
|
|
75
78
|
/// @title Host
|
|
76
79
|
/// @notice Abstract base contract for rootzero host implementations.
|
|
@@ -78,13 +81,7 @@ abstract contract Guardians is Appoint, Dismiss, Revoke {}
|
|
|
78
81
|
/// guardian management, the default guardian revoke action, and
|
|
79
82
|
/// optionally introduces itself to a commander host at deployment.
|
|
80
83
|
/// Accepts native ETH payments via the `receive` function.
|
|
81
|
-
abstract contract Host is
|
|
82
|
-
Admins,
|
|
83
|
-
Guardians,
|
|
84
|
-
HostIntroduction,
|
|
85
|
-
IntroductionEvent,
|
|
86
|
-
IHostIntroduction
|
|
87
|
-
{
|
|
84
|
+
abstract contract Host is Admins, Guardians, HostIntroduction, IntroductionEvent, IHostIntroduction {
|
|
88
85
|
/// @param cmdr Commander address; used by the composed access capabilities.
|
|
89
86
|
/// If `cmdr` is a deployed contract, the host calls `introduce`
|
|
90
87
|
/// on it during construction.
|
package/events/Dispatch.sol
CHANGED
|
@@ -8,7 +8,7 @@ abstract contract DispatchEvent is EventEmitter {
|
|
|
8
8
|
string private constant ABI = "event Dispatch(uint indexed host, uint portal, uint resources, bytes32 key, bytes32 digest)";
|
|
9
9
|
|
|
10
10
|
/// @param host Host node ID that owns the dispatch.
|
|
11
|
-
/// @param portal Destination portal
|
|
11
|
+
/// @param portal Destination portal implementation's host ID.
|
|
12
12
|
/// @param resources Chain-specific resources assigned to the dispatch.
|
|
13
13
|
/// @param key Dispatch correlation or recovery lookup key.
|
|
14
14
|
/// @param digest Digest of the dispatched payload or canonical envelope.
|
package/events/Relay.sol
CHANGED
|
@@ -8,7 +8,7 @@ abstract contract RelayEvent is EventEmitter {
|
|
|
8
8
|
string private constant ABI = "event Relay(bytes32 indexed account, uint portal, uint resources, bytes32 key, bytes32 digest)";
|
|
9
9
|
|
|
10
10
|
/// @param account Account that owns the relayed context.
|
|
11
|
-
/// @param portal Destination portal
|
|
11
|
+
/// @param portal Destination portal implementation's host ID.
|
|
12
12
|
/// @param resources Chain-specific resources assigned to the relay.
|
|
13
13
|
/// @param key Relay correlation or recovery lookup key.
|
|
14
14
|
/// @param digest Digest of the relayed payload or canonical envelope.
|
package/events/Route.sol
CHANGED
|
@@ -8,7 +8,7 @@ abstract contract RouteEvent is EventEmitter {
|
|
|
8
8
|
string private constant ABI = "event Route(uint indexed host, uint portal, uint status)";
|
|
9
9
|
|
|
10
10
|
/// @param host Host node ID that owns the route.
|
|
11
|
-
/// @param portal Destination portal
|
|
11
|
+
/// @param portal Destination portal implementation's host ID.
|
|
12
12
|
/// @param status Route status. Zero means inactive; nonzero means active.
|
|
13
13
|
event Route(uint indexed host, uint portal, uint status);
|
|
14
14
|
|
package/execution/Execution.sol
CHANGED
|
@@ -187,6 +187,25 @@ library Executions {
|
|
|
187
187
|
return exec.decoders.any();
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
/// @notice Return a decoder lane's current absolute calldata position.
|
|
191
|
+
/// @param exec Execution whose decoder is inspected.
|
|
192
|
+
/// @param lane Decoder lane to select.
|
|
193
|
+
/// @return Current absolute calldata position of the selected lane.
|
|
194
|
+
function absolute(Execution memory exec, uint8 lane) internal pure returns (uint) {
|
|
195
|
+
return exec.decoders.select(lane).absolute();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/// @notice Return whether the next block on `lane` has `key` and an empty payload.
|
|
199
|
+
/// @param exec Execution whose decoder is inspected without advancing.
|
|
200
|
+
/// @param lane Decoder lane to inspect.
|
|
201
|
+
/// @param key Expected block key.
|
|
202
|
+
/// @return Whether a complete matching empty block header occurs next.
|
|
203
|
+
function isEmpty(Execution memory exec, uint8 lane, bytes4 key) internal pure returns (bool) {
|
|
204
|
+
uint cur = exec.decoders.select(lane);
|
|
205
|
+
(uint i, uint offset, uint len) = cur.decode();
|
|
206
|
+
return Blocks.isEmpty(offset + i, offset + len, key);
|
|
207
|
+
}
|
|
208
|
+
|
|
190
209
|
/// @notice Validate and consume the next block from an execution decoder lane.
|
|
191
210
|
/// @param exec Execution whose selected decoder cursor is advanced over the complete block.
|
|
192
211
|
/// @param lane Execution decoder lane to select.
|
|
@@ -199,6 +218,20 @@ library Executions {
|
|
|
199
218
|
exec.decoders = cur.seekAbs(end);
|
|
200
219
|
}
|
|
201
220
|
|
|
221
|
+
/// @notice Consume a matching empty block from an execution decoder lane when present.
|
|
222
|
+
/// @param exec Execution whose selected decoder advances only for a matching empty block.
|
|
223
|
+
/// @param lane Decoder lane to consume.
|
|
224
|
+
/// @param key Expected block key.
|
|
225
|
+
/// @return Whether an empty block was consumed.
|
|
226
|
+
function tryConsumeEmpty(Execution memory exec, uint8 lane, bytes4 key) internal pure returns (bool) {
|
|
227
|
+
uint cur = exec.decoders.select(lane);
|
|
228
|
+
(uint i, uint offset, uint size) = cur.decode();
|
|
229
|
+
(bytes4 current, uint len) = Blocks.peek(offset + i, offset + size);
|
|
230
|
+
if (current != key || len != 0) return false;
|
|
231
|
+
exec.decoders = cur.seek(i + Sizes.Header);
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
|
|
202
235
|
/// @notice Validate and enter the payload of the next block in an execution decoder lane.
|
|
203
236
|
/// @dev The selected lane remains in its existing frame so callers can decode
|
|
204
237
|
/// child blocks in place. Callers should prove complete payload consumption
|
|
@@ -209,9 +242,48 @@ library Executions {
|
|
|
209
242
|
/// @return abs Absolute position of the first payload byte.
|
|
210
243
|
/// @return end Absolute position immediately after the payload.
|
|
211
244
|
function enter(Execution memory exec, uint8 lane, uint spec) internal pure returns (uint abs, uint end) {
|
|
245
|
+
return enter(exec, lane, spec, 0);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/// @notice Validate a parent block and advance over a fixed payload prefix.
|
|
249
|
+
/// @dev `amount` is relative to the payload start and cannot exceed the
|
|
250
|
+
/// current parent payload. The returned `abs` remains the payload start.
|
|
251
|
+
/// @param exec Execution whose selected decoder advances over the header and fixed prefix.
|
|
252
|
+
/// @param lane Execution decoder lane to select.
|
|
253
|
+
/// @param spec Expected parent block specification.
|
|
254
|
+
/// @param amount Number of initial payload bytes to advance over.
|
|
255
|
+
/// @return abs Absolute position of the first payload byte.
|
|
256
|
+
/// @return end Absolute position immediately after the payload.
|
|
257
|
+
function enter(
|
|
258
|
+
Execution memory exec,
|
|
259
|
+
uint8 lane,
|
|
260
|
+
uint spec,
|
|
261
|
+
uint amount
|
|
262
|
+
) internal pure returns (uint abs, uint end) {
|
|
212
263
|
uint cur = exec.decoders.select(lane);
|
|
213
264
|
(abs, end) = Blocks.expect(cur.absolute(), spec);
|
|
214
|
-
|
|
265
|
+
if (amount > end - abs) revert Blocks.InvalidBlock();
|
|
266
|
+
exec.decoders = cur.seekAbs(abs + amount);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/// @notice Advance an execution decoder lane by a raw byte count.
|
|
270
|
+
/// @dev No block header or schema is validated.
|
|
271
|
+
/// @param exec Execution whose selected decoder cursor is advanced.
|
|
272
|
+
/// @param lane Decoder lane to select.
|
|
273
|
+
/// @param amount Number of bytes to advance.
|
|
274
|
+
function advance(Execution memory exec, uint8 lane, uint amount) internal pure {
|
|
275
|
+
uint cur = exec.decoders.select(lane);
|
|
276
|
+
exec.decoders = cur.advance(amount);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/// @notice Take a raw byte range from an execution decoder lane.
|
|
280
|
+
/// @dev No block header or schema is validated.
|
|
281
|
+
/// @param exec Execution whose selected decoder cursor is advanced.
|
|
282
|
+
/// @param lane Decoder lane to select.
|
|
283
|
+
/// @param amount Number of bytes to take.
|
|
284
|
+
/// @return abs Absolute position of the first taken byte.
|
|
285
|
+
function take(Execution memory exec, uint8 lane, uint amount) internal pure returns (uint abs) {
|
|
286
|
+
(exec.decoders, abs) = exec.decoders.consume(lane, amount);
|
|
215
287
|
}
|
|
216
288
|
|
|
217
289
|
/// @notice Require the active execution decoder to be at absolute position `abs`.
|
|
@@ -246,9 +318,7 @@ library Executions {
|
|
|
246
318
|
|
|
247
319
|
/// @dev Return the next raw calldata word from `lane` and advance by `size` bytes.
|
|
248
320
|
function next(Execution memory exec, uint8 lane, uint size) private pure returns (bytes32 value) {
|
|
249
|
-
|
|
250
|
-
(exec.decoders, abs) = exec.decoders.consume(lane, size);
|
|
251
|
-
value = Blocks.read32(abs);
|
|
321
|
+
value = Blocks.read32(take(exec, lane, size));
|
|
252
322
|
}
|
|
253
323
|
|
|
254
324
|
/// @notice Return the next raw byte from a decoder lane and advance it by one byte.
|
|
@@ -806,6 +876,14 @@ library Executions {
|
|
|
806
876
|
return reserve(exec, size, size);
|
|
807
877
|
}
|
|
808
878
|
|
|
879
|
+
/// @notice Append an empty block to execution output.
|
|
880
|
+
/// @param exec Execution receiving the block.
|
|
881
|
+
/// @param key Block key.
|
|
882
|
+
function outputEmpty(Execution memory exec, bytes4 key) internal pure {
|
|
883
|
+
uint i = reserve(exec, Sizes.Header);
|
|
884
|
+
Blocks.writeEmpty(exec.output, i, key);
|
|
885
|
+
}
|
|
886
|
+
|
|
809
887
|
/// @notice Append an ACCOUNT block to execution output.
|
|
810
888
|
/// @param exec Execution receiving the block.
|
|
811
889
|
/// @param account Account identifier to encode.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rootzero/contracts",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.22.0",
|
|
4
4
|
"description": "Solidity contracts and protocol building blocks for rootzero hosts and commands.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "GPL-3.0-only",
|
|
@@ -9,8 +9,7 @@
|
|
|
9
9
|
"**/*.sol",
|
|
10
10
|
"README.md",
|
|
11
11
|
"CHANGELOG.md",
|
|
12
|
-
"LICENSE"
|
|
13
|
-
"docs/Schema.md"
|
|
12
|
+
"LICENSE"
|
|
14
13
|
],
|
|
15
14
|
"publishConfig": {
|
|
16
15
|
"access": "public"
|
package/ports/Dispatch.sol
CHANGED
|
@@ -11,7 +11,8 @@ using Executions for Execution;
|
|
|
11
11
|
/// @notice Hook implemented by hosts that forward funded dispatch payloads.
|
|
12
12
|
abstract contract DispatchPayableHook {
|
|
13
13
|
/// @notice Override to dispatch an encoded payload to `portal`.
|
|
14
|
-
/// @param portal Destination portal
|
|
14
|
+
/// @param portal Destination portal implementation's host ID. Implementations
|
|
15
|
+
/// may validate or resolve it for their transport.
|
|
15
16
|
/// @param resources Chain-specific destination resources. EVM adapters
|
|
16
17
|
/// may interpret this as packed execution gas and destination value.
|
|
17
18
|
/// @param payload Encoded payload ready for the transport layer.
|
package/docs/Schema.md
DELETED
|
@@ -1,427 +0,0 @@
|
|
|
1
|
-
# Schema
|
|
2
|
-
|
|
3
|
-
Rootzero input and response data is encoded as a stream of typed blocks. A
|
|
4
|
-
schema string describes the payload body for discovery events and tooling; the
|
|
5
|
-
runtime block key is the compact type tag that identifies that payload layout in
|
|
6
|
-
the active schema context. The block alias is published separately from the
|
|
7
|
-
payload schema.
|
|
8
|
-
|
|
9
|
-
## Wire Format
|
|
10
|
-
|
|
11
|
-
Every block uses the same header:
|
|
12
|
-
|
|
13
|
-
```txt
|
|
14
|
-
[bytes4 key][uint32 payloadLen][payload]
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
`payloadLen` is big-endian and counts only payload bytes. Child blocks and list
|
|
18
|
-
items use the same header format.
|
|
19
|
-
|
|
20
|
-
Standard built-in block keys use:
|
|
21
|
-
|
|
22
|
-
```txt
|
|
23
|
-
bytes4(keccak256("#name"))
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
For example, the standard `amount` alias uses the key derived from `#amount`
|
|
27
|
-
and the schema body `{ bytes32 asset, uint amount }`. Custom block keys do not
|
|
28
|
-
have to be keccak-derived. They
|
|
29
|
-
are opaque `bytes4` tags and only need to be unique in the context where they are
|
|
30
|
-
used. A host can publish the meaning of a custom key as an annotation:
|
|
31
|
-
|
|
32
|
-
```solidity
|
|
33
|
-
event Annotation(uint indexed entity, bytes data);
|
|
34
|
-
#schema { uint spec, #string as body, bytes32 name }
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
Annotation merge behavior is defined by the annotation block type rather than
|
|
38
|
-
by the `Annotation` event. A `#schema` annotation is identified by its entity
|
|
39
|
-
and the block key encoded in `spec`: distinct keys accumulate, while the latest
|
|
40
|
-
trusted claim for the same key replaces the earlier one. Other annotation types
|
|
41
|
-
may define additive, historical, or explicitly revocable behavior instead.
|
|
42
|
-
|
|
43
|
-
The standard `#action { uint action }` annotation assigns one primary semantic
|
|
44
|
-
action to an entity. The latest trusted value replaces the previous value, and
|
|
45
|
-
`Actions.None` clears the classification.
|
|
46
|
-
|
|
47
|
-
For example, a host-specific payment block can use a small literal, the command
|
|
48
|
-
selector, or any other chosen `bytes4` value as long as that key is not
|
|
49
|
-
overloaded in the relevant host/schema context.
|
|
50
|
-
|
|
51
|
-
## Block Syntax
|
|
52
|
-
|
|
53
|
-
A block definition has an event alias and a schema body. A schema body is one
|
|
54
|
-
of three forms:
|
|
55
|
-
|
|
56
|
-
```txt
|
|
57
|
-
"" empty or raw payload
|
|
58
|
-
{ fields } structured payload
|
|
59
|
-
many #item top-level custom list payload
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
Fixed fields are written in braces:
|
|
63
|
-
|
|
64
|
-
```txt
|
|
65
|
-
alias: amount
|
|
66
|
-
schema: { bytes32 asset, uint amount }
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
A block body can reference another block alias as a child item with `#`:
|
|
70
|
-
|
|
71
|
-
```txt
|
|
72
|
-
{ bytes32 account, #bytes as state, #bytes as input }
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
The empty schema string `""` means the block has no structured payload. This is
|
|
76
|
-
used for zero-payload blocks such as `#unit` and raw dynamic blocks such as
|
|
77
|
-
`#bytes`. A root `many #item` body is reserved for an emitted custom schema
|
|
78
|
-
whose own key identifies the outer list block.
|
|
79
|
-
|
|
80
|
-
A structured schema body is a comma-separated list of items. Order is
|
|
81
|
-
significant.
|
|
82
|
-
|
|
83
|
-
```txt
|
|
84
|
-
{ #amount, maybe #account as recipient }
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
## Payload Layout
|
|
88
|
-
|
|
89
|
-
A block payload encodes schema items in declaration order. Fixed fields are
|
|
90
|
-
packed inline, and zero or more child blocks are embedded directly at their
|
|
91
|
-
declared positions. Because every child block carries its own header and payload
|
|
92
|
-
length, fixed fields may appear before, after, or between child blocks.
|
|
93
|
-
|
|
94
|
-
```txt
|
|
95
|
-
{ uint target, uint resources, #bytes as payload }
|
|
96
|
-
{ bytes32 account, #bytes as state, #bytes as input }
|
|
97
|
-
{ uint spec, #string as body, bytes32 name }
|
|
98
|
-
{ #bytes as left, uint op, #bytes as right }
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
There is no wrapper around embedded child blocks.
|
|
102
|
-
|
|
103
|
-
Raw dynamic bytes are represented with the reserved `#bytes` child block. Use an
|
|
104
|
-
alias to give those bytes a presentation name:
|
|
105
|
-
|
|
106
|
-
```txt
|
|
107
|
-
#bytes as payload
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
## Modifiers
|
|
111
|
-
|
|
112
|
-
Cardinality is expressed with prefix keywords:
|
|
113
|
-
|
|
114
|
-
```txt
|
|
115
|
-
#balance
|
|
116
|
-
maybe #balance
|
|
117
|
-
many #balance
|
|
118
|
-
maybe many #balance
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
- no prefix: one required item
|
|
122
|
-
- `maybe`: optional item
|
|
123
|
-
- `many`: a list whose payload contains repeated items
|
|
124
|
-
- `maybe many`: optional `#list` block
|
|
125
|
-
|
|
126
|
-
`maybe` emits no placeholder when absent. Inside a structured schema body,
|
|
127
|
-
`many` wraps repeated items in one generic `#list` block; it does not repeat the
|
|
128
|
-
item in place:
|
|
129
|
-
|
|
130
|
-
```txt
|
|
131
|
-
{ uint id, many #asset as assets }
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
When an emitted custom schema consists entirely of a top-level `many` item, the
|
|
135
|
-
custom schema key identifies the outer list block instead. Its payload contains
|
|
136
|
-
the repeated items directly:
|
|
137
|
-
|
|
138
|
-
```txt
|
|
139
|
-
schema key: 0x00000001
|
|
140
|
-
schema body: many #asset
|
|
141
|
-
wire value: [0x00000001][length][ASSET][ASSET]...
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
This convention gives a top-level list a discoverable, context-local type while
|
|
145
|
-
retaining the generic `#list` key for lists whose type is supplied by an
|
|
146
|
-
enclosing schema.
|
|
147
|
-
|
|
148
|
-
## Endpoint Lanes
|
|
149
|
-
|
|
150
|
-
Endpoint descriptors identify each lane with a block key and group size. In
|
|
151
|
-
Solidity, endpoint definition helpers accept block specs such as `Specs.Amount`.
|
|
152
|
-
A zero group is interpreted as group size 1, while `Specs.Empty` means the
|
|
153
|
-
endpoint has no blocks in that lane. Use `group(spec, size)` when a lane needs
|
|
154
|
-
an explicit group size other than 1.
|
|
155
|
-
|
|
156
|
-
The packed descriptor uses these lane layouts:
|
|
157
|
-
|
|
158
|
-
```txt
|
|
159
|
-
state [key:4][group:1]
|
|
160
|
-
input [key:4][group:1]
|
|
161
|
-
output [key:4][min:4][max:4][hint:3][group:1]
|
|
162
|
-
reserve [reserved:4]
|
|
163
|
-
tx [transactions:1]
|
|
164
|
-
flags [flags:1]
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
Each lane directly identifies its top-level block key. Output lanes retain their
|
|
168
|
-
size bounds and allocation hint so execution can reconstruct the output spec and
|
|
169
|
-
initialize its writer directly. Four descriptor-level bytes are reserved after
|
|
170
|
-
the lane metadata. The Solidity output decoder returns a left-aligned,
|
|
171
|
-
writer-ready spec that retains its encoded group and clears its reserved fields.
|
|
172
|
-
`Specs.group` returns the effective group, interpreting an encoded zero as one
|
|
173
|
-
for a non-empty spec.
|
|
174
|
-
|
|
175
|
-
Any non-empty lane resolves its key to a block alias and schema body through the
|
|
176
|
-
active schema context. A top-level list lane uses the key of its emitted custom
|
|
177
|
-
`many` schema; the descriptor treats it like every other direct lane spec.
|
|
178
|
-
|
|
179
|
-
The lane key is the prime item. Prime items may repeat at the top level for
|
|
180
|
-
batching. Later top-level items are globals for the whole batch and are not
|
|
181
|
-
counted as per-operation prime blocks.
|
|
182
|
-
|
|
183
|
-
The prime item cannot be optional. If an endpoint needs a per-operation marker
|
|
184
|
-
with no payload, use a zero-payload block such as `#unit`.
|
|
185
|
-
|
|
186
|
-
Endpoint descriptors currently use a narrower convention than the full block
|
|
187
|
-
grammar: each state, input, or output lane is a single run of blocks, without
|
|
188
|
-
additional global items. Endpoint decoder opening requires that run to consume
|
|
189
|
-
the complete supplied lane; a trailing block with another key is invalid.
|
|
190
|
-
Lower-level cursor scanning may still intentionally open only a prefix run.
|
|
191
|
-
Those lower layers retain only the raw block count; descriptor strides are
|
|
192
|
-
applied and lane groups reconciled once when an endpoint execution opens.
|
|
193
|
-
Future protocol surfaces may use the more flexible top-level structure.
|
|
194
|
-
|
|
195
|
-
For commands, complete-lane validation is also a state-safety rule. State is a
|
|
196
|
-
linear value owned by the current pipeline step, not optional context that a
|
|
197
|
-
command may disregard. Every command must account for the complete supplied
|
|
198
|
-
state by consuming it, transforming and returning it, forwarding it intact, or
|
|
199
|
-
reverting. A command whose descriptor declares an empty state lane must reject
|
|
200
|
-
non-empty state. A command that accepts state must validate the complete stream
|
|
201
|
-
against its declared schema; accepting only a prefix and silently dropping the
|
|
202
|
-
remainder is invalid.
|
|
203
|
-
|
|
204
|
-
## Live Pipeline State
|
|
205
|
-
|
|
206
|
-
`#balance`, `#custody`, and `#position` are live state carried between command
|
|
207
|
-
steps for the active account. A position atomically pairs an asset side with a
|
|
208
|
-
liability side:
|
|
209
|
-
|
|
210
|
-
```txt
|
|
211
|
-
position { bytes32 asset, uint amount, bytes32 liability, uint debt }
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
The pair is deliberately general. The asset side represents value acquired or
|
|
215
|
-
controlled, and the liability side represents value owed or required. Commands
|
|
216
|
-
may preserve or replace either side and return a new position. The terminal
|
|
217
|
-
`settle` command consumes the pair. Position state is transient protocol state;
|
|
218
|
-
rewriting it does not by itself create, discharge, or replace an obligation
|
|
219
|
-
persisted by a host or external protocol. The responsible command hook must
|
|
220
|
-
perform or verify those effects. A command must not ignore a supplied position:
|
|
221
|
-
it must explicitly consume, transform, forward, or reject it, so neither its
|
|
222
|
-
asset nor its debt can disappear accidentally.
|
|
223
|
-
|
|
224
|
-
This representation supports ordinary forward transformations as well as
|
|
225
|
-
backward composition. For example, an exact-output route can carry its desired
|
|
226
|
-
asset while successive hops replace the upstream liability:
|
|
227
|
-
|
|
228
|
-
```txt
|
|
229
|
-
position(C, 100, C, 100)
|
|
230
|
-
→ position(C, 100, B, 50)
|
|
231
|
-
→ position(C, 100, A, 25)
|
|
232
|
-
→ settle
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
“Backward” describes how requirements are composed from the desired result
|
|
236
|
-
toward the source. Pipeline execution is not reversed: STEP blocks always run
|
|
237
|
-
forward in their encoded order. Exact-output routing is only an example;
|
|
238
|
-
borrowing, refinancing, collateral transformation, callback obligations,
|
|
239
|
-
cross-host claims, fees, and netting can use the same position state.
|
|
240
|
-
|
|
241
|
-
## Field Aliases
|
|
242
|
-
|
|
243
|
-
Block aliases are published in `#schema` annotations. Field aliases are presentation
|
|
244
|
-
metadata for tooling. They do not change payload layout or runtime keys.
|
|
245
|
-
|
|
246
|
-
```txt
|
|
247
|
-
maybe #account as recipient
|
|
248
|
-
{ uint target, uint resources, #bytes as payload }
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
Field aliases may be used on any block item, including child blocks and prime
|
|
252
|
-
items.
|
|
253
|
-
|
|
254
|
-
Child blocks are schema references:
|
|
255
|
-
|
|
256
|
-
```txt
|
|
257
|
-
{ uint handler, uint resources, bytes32 key, #bytes as witness }
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
Alias resolution is context-dependent. A consumer may resolve `#context` from
|
|
261
|
-
standard block events, from app-specific block events, or from another active
|
|
262
|
-
schema context. Custom parents should define nested custom blocks from the
|
|
263
|
-
bottom up and reference them by alias. Consumers should reject schemas with
|
|
264
|
-
unresolved aliases. The runtime encoding is still an embedded child block with
|
|
265
|
-
the referenced key and layout.
|
|
266
|
-
|
|
267
|
-
## Field Paths
|
|
268
|
-
|
|
269
|
-
Field names and aliases may use dotted paths for offchain projection. A dotted
|
|
270
|
-
path does not change the block key, payload bytes, payload length, cursor
|
|
271
|
-
behavior, or any onchain validation. It is metadata only.
|
|
272
|
-
|
|
273
|
-
```txt
|
|
274
|
-
{ uint dst.portal, uint dst.resources, #bytes as dst.payload }
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
This has the same runtime layout as:
|
|
278
|
-
|
|
279
|
-
```txt
|
|
280
|
-
{ uint portal, uint resources, #bytes as payload }
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
Offchain tooling may decode the dotted form into a nested object:
|
|
284
|
-
|
|
285
|
-
```ts
|
|
286
|
-
{
|
|
287
|
-
dst: {
|
|
288
|
-
portal,
|
|
289
|
-
resources,
|
|
290
|
-
payload
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
Encoding and decoding must still follow schema declaration order, not object
|
|
296
|
-
property order. Fields with the same path prefix do not need to be contiguous,
|
|
297
|
-
although contiguous fields are easier to read when they represent one logical
|
|
298
|
-
object.
|
|
299
|
-
|
|
300
|
-
Tooling should reject duplicate full paths and prefix/value collisions:
|
|
301
|
-
|
|
302
|
-
```txt
|
|
303
|
-
uint dst.portal, uint dst.portal // duplicate path
|
|
304
|
-
uint dst, uint dst.portal // prefix/value collision
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
The same rule applies to field aliases:
|
|
308
|
-
|
|
309
|
-
```txt
|
|
310
|
-
{ uint target, uint resources, #bytes as calldata.payload }
|
|
311
|
-
maybe #account as recipient.account
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
## Field Types
|
|
315
|
-
|
|
316
|
-
Supported field types are chain-neutral:
|
|
317
|
-
|
|
318
|
-
```txt
|
|
319
|
-
uint, uint8, uint16, uint32, uint64, uint128, uint256
|
|
320
|
-
int, int8, int16, int32, int64, int128, int256
|
|
321
|
-
bool
|
|
322
|
-
bytes1 through bytes32
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
`uint` means `uint256`; `int` means `int256`. Other integer widths, unsized
|
|
326
|
-
`bytes`, `string`, and array syntax are not part of the core schema DSL.
|
|
327
|
-
|
|
328
|
-
Restricting fixed bytes to the power-of-two widths `bytes1`, `bytes2`,
|
|
329
|
-
`bytes4`, `bytes8`, `bytes16`, and `bytes32` is under consideration, but has
|
|
330
|
-
not been decided. Until that decision is made, the schema DSL continues to
|
|
331
|
-
allow every `bytesN` width from 1 through 32.
|
|
332
|
-
|
|
333
|
-
Integers are encoded big-endian. Signed integers use two's-complement encoding
|
|
334
|
-
for their declared width. `bool` is one byte: `0x00` for false and `0x01` for
|
|
335
|
-
true. `bytesN` values are encoded as exactly `N` bytes with no padding.
|
|
336
|
-
|
|
337
|
-
## Chain Resources
|
|
338
|
-
|
|
339
|
-
Fields named `portal` are routing identifiers; they are often the destination
|
|
340
|
-
host ID, but a transport adapter may define a different stable handle.
|
|
341
|
-
|
|
342
|
-
Fields named `resources` are chain-specific resource words. A portal adapter
|
|
343
|
-
interprets them for the destination runtime. Different runtimes may pack these
|
|
344
|
-
words differently, but a given runtime must use one stable format everywhere.
|
|
345
|
-
For EVM chains, the low 128 bits are native value / endowment in wei; higher
|
|
346
|
-
bits are reserved for execution resources such as gas.
|
|
347
|
-
|
|
348
|
-
## Protocol IDs
|
|
349
|
-
|
|
350
|
-
Account, asset, and node ID fields use one 32-byte convention:
|
|
351
|
-
|
|
352
|
-
- first byte `0x00`: opaque ID, encoded as `0x00 || bytes31(hash)`. The full
|
|
353
|
-
preimage must come from a lookup table or witness data when native metadata is
|
|
354
|
-
needed.
|
|
355
|
-
- first byte nonzero: structured ID. The value may be deconstructed according
|
|
356
|
-
to its chain/runtime layout.
|
|
357
|
-
|
|
358
|
-
Opaque preimages must start with a one-byte format/hash tag; `0x01` means
|
|
359
|
-
keccak256. The remaining bytes are host/domain-specific until the protocol
|
|
360
|
-
standardizes a fuller preimage payload format.
|
|
361
|
-
|
|
362
|
-
The field name supplies the protocol role for opaque IDs. For example, a
|
|
363
|
-
`bytes32 asset` whose first byte is zero is still an asset in that block; it
|
|
364
|
-
just cannot be decoded without external context. Runtime helpers that inspect
|
|
365
|
-
the layout of an ID only apply to structured IDs.
|
|
366
|
-
|
|
367
|
-
## Identifiers
|
|
368
|
-
|
|
369
|
-
Block aliases use lower camelCase ASCII identifiers. Field names and aliases
|
|
370
|
-
use one or more lower camelCase path segments separated by dots:
|
|
371
|
-
|
|
372
|
-
```txt
|
|
373
|
-
[a-z][a-zA-Z0-9]*
|
|
374
|
-
[a-z][a-zA-Z0-9]*(\.[a-z][a-zA-Z0-9]*)*
|
|
375
|
-
```
|
|
376
|
-
|
|
377
|
-
Invalid examples:
|
|
378
|
-
|
|
379
|
-
```txt
|
|
380
|
-
Amount
|
|
381
|
-
asset_meta
|
|
382
|
-
asset-meta
|
|
383
|
-
0account
|
|
384
|
-
asset.
|
|
385
|
-
.asset
|
|
386
|
-
```
|
|
387
|
-
|
|
388
|
-
Reserved words include `maybe`, `many`, `as`, all field type names, and the
|
|
389
|
-
reserved block aliases `bytes` and `list`. For dotted paths, reserved words are
|
|
390
|
-
invalid in any path segment.
|
|
391
|
-
|
|
392
|
-
## Reserved Blocks
|
|
393
|
-
|
|
394
|
-
- `#bytes`: raw dynamic bytes, written without a body
|
|
395
|
-
- `#string`: UTF-8 string bytes, written without a body
|
|
396
|
-
- `#list`: generic list wrapper emitted by nested `many`
|
|
397
|
-
|
|
398
|
-
Custom input shapes should define their own context-local block spec and publish
|
|
399
|
-
it with a `#schema` annotation. Endpoint contracts can use `schema(...)` to
|
|
400
|
-
construct and publish that spec:
|
|
401
|
-
|
|
402
|
-
```solidity
|
|
403
|
-
uint input = schema(1, 64, 64, 64, "{ bytes32 asset, uint amount }", bytes32(0));
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
Use different numeric keys when a host needs more than one local block key. The
|
|
407
|
-
key can also be a selector or any other `bytes4` value that is unique in the
|
|
408
|
-
context where it is used. The numeric arguments after the key are the minimum,
|
|
409
|
-
maximum, and allocation hint payload sizes. The alias names the block; the
|
|
410
|
-
schema string describes only the payload body.
|
|
411
|
-
|
|
412
|
-
## Standard Blocks
|
|
413
|
-
|
|
414
|
-
Common protocol schemas live in `contracts/codec/Schema.sol`:
|
|
415
|
-
|
|
416
|
-
```txt
|
|
417
|
-
amount { bytes32 asset, uint amount }
|
|
418
|
-
balance { bytes32 asset, uint amount }
|
|
419
|
-
custody { uint host, bytes32 asset, uint amount }
|
|
420
|
-
call { uint target, uint resources, #bytes as payload }
|
|
421
|
-
step { uint cmd, uint resources, #bytes as input }
|
|
422
|
-
context { bytes32 account, #bytes as state, #bytes as input }
|
|
423
|
-
recover { uint handler, uint resources, bytes32 key, #bytes as witness }
|
|
424
|
-
schema { uint spec, #string as body, bytes32 name }
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
`Keys.sol` contains the corresponding standard runtime keys.
|