@rootzero/contracts 1.10.0 → 1.12.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 +46 -1
- package/Core.sol +1 -0
- package/Endpoints.sol +2 -3
- package/Events.sol +2 -5
- package/README.md +38 -36
- package/Utils.sol +2 -1
- package/blocks/Cursors.sol +39 -75
- package/blocks/Keys.sol +18 -4
- package/blocks/Schema.sol +53 -47
- package/commands/Base.sol +43 -13
- package/commands/Burn.sol +3 -6
- package/commands/Credit.sol +4 -6
- package/commands/Debit.sol +10 -11
- package/commands/Deposit.sol +19 -23
- package/commands/Payout.sol +6 -10
- package/commands/Provision.sol +19 -23
- package/commands/Recover.sol +7 -9
- package/commands/Relay.sol +10 -11
- package/commands/Withdraw.sol +3 -6
- package/commands/admin/AllowAssets.sol +7 -10
- package/commands/admin/Allowance.sol +7 -10
- package/commands/admin/Appoint.sol +7 -10
- package/commands/admin/Authorize.sol +10 -19
- package/commands/admin/Base.sol +1 -2
- package/commands/admin/DenyAssets.sol +7 -10
- package/commands/admin/Dismiss.sol +7 -10
- package/commands/admin/Execute.sol +7 -10
- package/commands/admin/Label.sol +8 -11
- package/commands/admin/Schemas.sol +31 -0
- package/commands/admin/Unauthorize.sol +10 -19
- package/core/Endpoint.sol +160 -0
- package/core/Host.sol +1 -1
- package/docs/Schema.md +124 -86
- package/events/Endpoint.sol +19 -0
- package/events/Schema.sol +23 -0
- package/guards/Base.sol +17 -8
- package/guards/Revoke.sol +4 -6
- package/package.json +1 -1
- package/ports/AllowAssets.sol +6 -9
- package/ports/Allowance.sol +6 -9
- package/ports/Base.sol +21 -8
- package/ports/Credit.sol +6 -9
- package/ports/Debit.sol +6 -9
- package/ports/DenyAssets.sol +6 -9
- package/ports/Dispatch.sol +6 -9
- package/ports/Pipe.sol +4 -7
- package/ports/Redeem.sol +4 -7
- package/ports/Settle.sol +6 -9
- package/queries/Assets.sol +9 -12
- package/queries/Balances.sol +7 -9
- package/queries/Base.sol +19 -11
- package/utils/Selectors.sol +49 -0
- package/commands/admin/Destroy.sol +0 -43
- package/commands/admin/Init.sol +0 -43
- package/events/Admin.sol +0 -32
- package/events/Command.sol +0 -32
- package/events/Guard.sol +0 -18
- package/events/Port.sol +0 -22
- package/events/Query.sol +0 -20
- package/queries/Positions.sol +0 -54
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
|
+
pragma solidity ^0.8.33;
|
|
3
|
+
|
|
4
|
+
import {Cursors, Cur} from "../Cursors.sol";
|
|
5
|
+
import {Keys} from "../blocks/Keys.sol";
|
|
6
|
+
import {EndpointEvent} from "../events/Endpoint.sol";
|
|
7
|
+
import {LabeledEvent} from "../events/Labeled.sol";
|
|
8
|
+
import {SchemaEvent} from "../events/Schema.sol";
|
|
9
|
+
import {Runtime} from "./Runtime.sol";
|
|
10
|
+
|
|
11
|
+
/// @title Lane
|
|
12
|
+
/// @notice Bit offsets for endpoint descriptor lanes.
|
|
13
|
+
library Lane {
|
|
14
|
+
/// @dev Descriptor shift for the state lane.
|
|
15
|
+
uint internal constant State = 184;
|
|
16
|
+
/// @dev Descriptor shift for the input lane.
|
|
17
|
+
uint internal constant Input = 112;
|
|
18
|
+
/// @dev Descriptor shift for the output lane.
|
|
19
|
+
uint internal constant Output = 40;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/// @title EndpointBase
|
|
23
|
+
/// @notice Shared endpoint metadata helpers.
|
|
24
|
+
abstract contract EndpointBase is Runtime, EndpointEvent, LabeledEvent, SchemaEvent {
|
|
25
|
+
/// @dev Pack endpoint lanes and flags into a descriptor.
|
|
26
|
+
/// A non-empty lane with group 0 defaults to group 1; a zero lane is absent.
|
|
27
|
+
/// Layout: `[state:8][group:1][input:8][group:1][output:8][group:1]`
|
|
28
|
+
/// `[flags:1][reserved:4]`. Flag bits: funded = 0, admin = 1.
|
|
29
|
+
/// @param state Packed state lane plus optional group byte.
|
|
30
|
+
/// @param input Packed input lane plus optional group byte.
|
|
31
|
+
/// @param output Packed output lane plus optional group byte.
|
|
32
|
+
/// @param funded Whether the endpoint accepts nonzero native value.
|
|
33
|
+
/// @param admin Whether the endpoint is restricted to the admin account.
|
|
34
|
+
/// @return value Packed endpoint descriptor as an integer.
|
|
35
|
+
function pack(
|
|
36
|
+
bytes9 state,
|
|
37
|
+
bytes9 input,
|
|
38
|
+
bytes9 output,
|
|
39
|
+
bool funded,
|
|
40
|
+
bool admin
|
|
41
|
+
) private pure returns (uint value) {
|
|
42
|
+
value |= uint(uint72(state)) << Lane.State;
|
|
43
|
+
value |= uint(uint72(input)) << Lane.Input;
|
|
44
|
+
value |= uint(uint72(output)) << Lane.Output;
|
|
45
|
+
value |= uint(funded ? 1 : 0) << 32;
|
|
46
|
+
value |= uint(admin ? 1 : 0) << 33;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// @dev Return a lane's effective group size, defaulting non-empty lanes to one.
|
|
50
|
+
/// @param descriptor Packed endpoint descriptor.
|
|
51
|
+
/// @param shift Bit offset of the lane to inspect.
|
|
52
|
+
/// @return size Effective group size, or zero when the lane is absent.
|
|
53
|
+
function laneGroup(bytes32 descriptor, uint shift) private pure returns (uint8 size) {
|
|
54
|
+
uint72 lane = uint72(uint(descriptor) >> shift);
|
|
55
|
+
if (lane == 0) return 0;
|
|
56
|
+
|
|
57
|
+
size = uint8(lane);
|
|
58
|
+
if (size == 0) size = 1;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/// @notice Return an 8-byte lane value for a generic LIST containing `item`.
|
|
62
|
+
/// @param item Block key expected inside each LIST payload.
|
|
63
|
+
/// @return Packed lane key `[Keys.List][item]`.
|
|
64
|
+
function many(bytes4 item) internal pure returns (bytes8) {
|
|
65
|
+
return bytes8(bytes.concat(Keys.List, item));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/// @notice Append an explicit group size to an 8-byte lane value.
|
|
69
|
+
/// @param value Packed lane key `[key][item]`.
|
|
70
|
+
/// @param size Explicit per-operation group size for the lane.
|
|
71
|
+
/// @return Packed lane key plus group byte.
|
|
72
|
+
function group(bytes8 value, uint8 size) internal pure returns (bytes9) {
|
|
73
|
+
return bytes9(bytes.concat(value, bytes1(size)));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// @dev Open a descriptor lane and return its effective group and output counts.
|
|
77
|
+
/// An absent lane inherits `expected`; a present lane must match it when nonzero.
|
|
78
|
+
/// @param source Block stream to open for the requested lane.
|
|
79
|
+
/// @param descriptor Packed endpoint descriptor.
|
|
80
|
+
/// @param shift Bit offset of the lane to open.
|
|
81
|
+
/// @param expected Required group count, or zero to accept the lane's count.
|
|
82
|
+
/// @return cur Cursor scoped to the lane's first block run.
|
|
83
|
+
/// @return groups Number of lane groups in `cur`, or `expected` for an absent lane.
|
|
84
|
+
/// @return outputs Number of output blocks implied by `groups` and the descriptor output lane.
|
|
85
|
+
function openLane(
|
|
86
|
+
bytes calldata source,
|
|
87
|
+
bytes32 descriptor,
|
|
88
|
+
uint shift,
|
|
89
|
+
uint expected
|
|
90
|
+
) internal pure returns (Cur memory cur, uint groups, uint outputs) {
|
|
91
|
+
(cur, groups) = Cursors.init(source, laneGroup(descriptor, shift));
|
|
92
|
+
if (groups == 0) groups = expected;
|
|
93
|
+
else if (expected != 0 && groups != expected) revert Cursors.BadRatio();
|
|
94
|
+
outputs = groups * laneGroup(descriptor, Lane.Output);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/// @notice Open an endpoint state stream and return the expected output block count.
|
|
98
|
+
/// @param source State block stream to open.
|
|
99
|
+
/// @param descriptor Packed endpoint descriptor.
|
|
100
|
+
/// @return state Cursor scoped to the state lane's first block run.
|
|
101
|
+
/// @return outputs Number of output blocks implied by the state group count.
|
|
102
|
+
function openState(
|
|
103
|
+
bytes calldata source,
|
|
104
|
+
bytes32 descriptor
|
|
105
|
+
) internal pure returns (Cur memory state, uint outputs) {
|
|
106
|
+
(state, , outputs) = openLane(source, descriptor, Lane.State, 0);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// @notice Open an endpoint input stream and return the expected output block count.
|
|
110
|
+
/// @param source Input block stream to open.
|
|
111
|
+
/// @param descriptor Packed endpoint descriptor.
|
|
112
|
+
/// @return input Cursor scoped to the input lane's first block run.
|
|
113
|
+
/// @return outputs Number of output blocks implied by the input group count.
|
|
114
|
+
function openInput(
|
|
115
|
+
bytes calldata source,
|
|
116
|
+
bytes32 descriptor
|
|
117
|
+
) internal pure returns (Cur memory input, uint outputs) {
|
|
118
|
+
(input, , outputs) = openLane(source, descriptor, Lane.Input, 0);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/// @notice Publish the default local block schema and return `Keys.Local`.
|
|
122
|
+
/// @param body Schema DSL string describing the block payload body.
|
|
123
|
+
/// @return The default context-local block key.
|
|
124
|
+
function localSchema(string memory body) internal returns (bytes4) {
|
|
125
|
+
return localSchema(1, body);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/// @notice Publish a context-local block schema and return its key.
|
|
129
|
+
/// @param key Context-local key value.
|
|
130
|
+
/// @param body Schema DSL string describing the block payload body.
|
|
131
|
+
/// @return The context-local block key.
|
|
132
|
+
function localSchema(uint32 key, string memory body) internal returns (bytes4) {
|
|
133
|
+
bytes4 k = Keys.local(key);
|
|
134
|
+
emit Schema(host, k, body, bytes32(0));
|
|
135
|
+
return k;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/// @notice Create and publish endpoint metadata with a default label.
|
|
139
|
+
/// @param id Endpoint node ID.
|
|
140
|
+
/// @param name Default human-readable endpoint label.
|
|
141
|
+
/// @param state Packed state lane plus optional group byte.
|
|
142
|
+
/// @param input Packed input lane plus optional group byte.
|
|
143
|
+
/// @param output Packed output lane plus optional group byte.
|
|
144
|
+
/// @param funded Whether the endpoint accepts nonzero native value.
|
|
145
|
+
/// @param admin Whether the endpoint is restricted to the admin account.
|
|
146
|
+
/// @return descriptor Packed endpoint lane metadata and flags.
|
|
147
|
+
function endpoint(
|
|
148
|
+
uint id,
|
|
149
|
+
string memory name,
|
|
150
|
+
bytes9 state,
|
|
151
|
+
bytes9 input,
|
|
152
|
+
bytes9 output,
|
|
153
|
+
bool funded,
|
|
154
|
+
bool admin
|
|
155
|
+
) internal returns (bytes32 descriptor) {
|
|
156
|
+
descriptor = bytes32(pack(state, input, output, funded, admin));
|
|
157
|
+
emit Endpoint(host, id, descriptor);
|
|
158
|
+
emit Labeled(id, bytes32(0), name);
|
|
159
|
+
}
|
|
160
|
+
}
|
package/core/Host.sol
CHANGED
|
@@ -23,7 +23,7 @@ interface IHostIntroduction {
|
|
|
23
23
|
|
|
24
24
|
/// @title Host
|
|
25
25
|
/// @notice Abstract base contract for rootzero host implementations.
|
|
26
|
-
/// Inherits admin command support (authorize, unauthorize, executePayable),
|
|
26
|
+
/// Inherits admin command support (authorize, unauthorize, label, executePayable),
|
|
27
27
|
/// guardian management, the default guardian revoke action, and
|
|
28
28
|
/// optionally introduces itself to a commander host at deployment.
|
|
29
29
|
/// Accepts native ETH payments via the `receive` function.
|
package/docs/Schema.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# Schema
|
|
2
2
|
|
|
3
3
|
Rootzero request and response data is encoded as a stream of typed blocks. A
|
|
4
|
-
schema string describes payload
|
|
5
|
-
runtime block key is
|
|
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.
|
|
6
8
|
|
|
7
9
|
## Wire Format
|
|
8
10
|
|
|
@@ -15,53 +17,67 @@ Every block uses the same header:
|
|
|
15
17
|
`payloadLen` is big-endian and counts only payload bytes. Child blocks and list
|
|
16
18
|
items use the same header format.
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
Standard built-in block keys use:
|
|
19
21
|
|
|
20
22
|
```txt
|
|
21
23
|
bytes4(keccak256("#name"))
|
|
22
24
|
```
|
|
23
25
|
|
|
24
|
-
For example,
|
|
25
|
-
|
|
26
|
-
|
|
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 with:
|
|
31
|
+
|
|
32
|
+
```solidity
|
|
33
|
+
event Schema(uint indexed host, bytes4 key, string schema, bytes32 name);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
For example, a host-specific payment block can use `Keys.Local`, the command
|
|
37
|
+
selector, or any other chosen `bytes4` value as long as that key is not
|
|
38
|
+
overloaded in the relevant host/schema context.
|
|
27
39
|
|
|
28
40
|
## Block Syntax
|
|
29
41
|
|
|
30
|
-
A block
|
|
42
|
+
A block definition has an event alias and a schema body. Fixed fields are
|
|
43
|
+
written in braces:
|
|
31
44
|
|
|
32
45
|
```txt
|
|
33
|
-
|
|
34
|
-
|
|
46
|
+
alias: amount
|
|
47
|
+
schema: { bytes32 asset, uint amount }
|
|
35
48
|
```
|
|
36
49
|
|
|
37
|
-
A block
|
|
50
|
+
A block body can reference another block alias as a child item with `#`:
|
|
38
51
|
|
|
39
52
|
```txt
|
|
40
|
-
#
|
|
41
|
-
#bytes
|
|
53
|
+
{ bytes32 account, #bytes as state, #bytes as request }
|
|
42
54
|
```
|
|
43
55
|
|
|
44
|
-
|
|
56
|
+
The empty schema string `""` means the block has no structured payload. This is
|
|
57
|
+
used for zero-payload blocks such as `#unit` and raw dynamic blocks such as
|
|
58
|
+
`#bytes`.
|
|
45
59
|
|
|
46
|
-
A schema is a comma-separated list of items. Order is significant.
|
|
60
|
+
A schema body is a comma-separated list of items. Order is significant.
|
|
47
61
|
|
|
48
62
|
```txt
|
|
49
|
-
#amount
|
|
50
|
-
maybe #account { bytes32 account }
|
|
63
|
+
{ #amount, maybe #account as recipient }
|
|
51
64
|
```
|
|
52
65
|
|
|
53
66
|
## Payload Layout
|
|
54
67
|
|
|
55
|
-
A block payload
|
|
56
|
-
|
|
68
|
+
A block payload encodes schema items in declaration order. Fixed fields are
|
|
69
|
+
packed inline, and zero or more child blocks are embedded directly at their
|
|
70
|
+
declared positions. Because every child block carries its own header and payload
|
|
71
|
+
length, fixed fields may appear before, after, or between child blocks.
|
|
57
72
|
|
|
58
73
|
```txt
|
|
59
|
-
|
|
60
|
-
|
|
74
|
+
{ uint target, uint resources, #bytes as payload }
|
|
75
|
+
{ bytes32 account, #bytes as state, #bytes as request }
|
|
76
|
+
{ bytes4 key, #string as body, bytes32 name }
|
|
77
|
+
{ #bytes as left, uint op, #bytes as right }
|
|
61
78
|
```
|
|
62
79
|
|
|
63
|
-
|
|
64
|
-
child-block tail.
|
|
80
|
+
There is no wrapper around embedded child blocks.
|
|
65
81
|
|
|
66
82
|
Raw dynamic bytes are represented with the reserved `#bytes` child block. Use an
|
|
67
83
|
alias to give those bytes a presentation name:
|
|
@@ -75,59 +91,87 @@ alias to give those bytes a presentation name:
|
|
|
75
91
|
Cardinality is expressed with prefix keywords:
|
|
76
92
|
|
|
77
93
|
```txt
|
|
78
|
-
#balance
|
|
79
|
-
maybe #balance
|
|
80
|
-
many #balance
|
|
81
|
-
maybe many #balance
|
|
94
|
+
#balance
|
|
95
|
+
maybe #balance
|
|
96
|
+
many #balance
|
|
97
|
+
maybe many #balance
|
|
82
98
|
```
|
|
83
99
|
|
|
84
100
|
- no prefix: one required item
|
|
85
101
|
- `maybe`: optional item
|
|
86
|
-
- `many`: one `#list` block whose payload contains repeated items
|
|
102
|
+
- `many`: one generic `#list` block whose payload contains repeated items
|
|
87
103
|
- `maybe many`: optional `#list` block
|
|
88
104
|
|
|
89
105
|
`maybe` emits no placeholder when absent. `many` wraps repeated items in one
|
|
90
106
|
generic list block; it does not repeat the item in place.
|
|
91
107
|
|
|
92
|
-
##
|
|
108
|
+
## Endpoint Lanes
|
|
109
|
+
|
|
110
|
+
Endpoint descriptors identify each lane with a block key and group size. In
|
|
111
|
+
Solidity, endpoint definition helpers accept `bytes9` lane values, with plain
|
|
112
|
+
`bytes4` keys and the `bytes8` values returned by `many(item)` widening
|
|
113
|
+
implicitly. A plain key or `many(item)` stores a zero group byte that readers
|
|
114
|
+
interpret as group size 1, while `bytes9(0)` or `Keys.Empty` means the endpoint
|
|
115
|
+
has no blocks in that lane. Use
|
|
116
|
+
`group(lane, size)` when a lane needs an explicit group size other than 1.
|
|
117
|
+
|
|
118
|
+
The packed descriptor stores each lane key as an 8-byte value:
|
|
119
|
+
|
|
120
|
+
```txt
|
|
121
|
+
[key bytes4][item bytes4]
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
A plain block key is widened into `[key][0]`, so normal endpoint declarations can
|
|
125
|
+
pass standard `bytes4` keys directly. A lane with a nonzero `item` describes a
|
|
126
|
+
generic container block: `key` is the top-level wire key and `item` is the
|
|
127
|
+
contained item key. The built-in `many(item)` helper creates `[Keys.List][item]`
|
|
128
|
+
with the default group size 1, matching the DSL form `many #item`.
|
|
93
129
|
|
|
94
|
-
|
|
130
|
+
Any non-empty lane resolves its key to a block alias and schema body through the
|
|
131
|
+
active schema context. If the item slot is nonzero, tooling also resolves that
|
|
132
|
+
item key in the same context. A bare list lane, `[Keys.List][0]`, is incomplete
|
|
133
|
+
discovery metadata because it does not say what the list contains; indexers
|
|
134
|
+
should reject it for self-describing endpoints.
|
|
95
135
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
136
|
+
The lane key is the prime item. Prime items may repeat at the top level for
|
|
137
|
+
batching. When the lane is `many #item`, the repeated prime item is the generic
|
|
138
|
+
LIST block and each LIST payload contains repeated `item` blocks. Later
|
|
139
|
+
top-level items are globals for the whole batch and are not counted as
|
|
140
|
+
per-operation prime blocks.
|
|
99
141
|
|
|
100
|
-
The prime item cannot be optional. If
|
|
101
|
-
no payload, use a zero-payload block such as `#unit`.
|
|
142
|
+
The prime item cannot be optional. If an endpoint needs a per-operation marker
|
|
143
|
+
with no payload, use a zero-payload block such as `#unit`.
|
|
102
144
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
items. Future protocol surfaces may use the more flexible
|
|
106
|
-
|
|
145
|
+
Endpoint descriptors currently use a narrower convention than the full block
|
|
146
|
+
grammar: each state, input, or output lane is a single run of blocks, without
|
|
147
|
+
additional global items. Future protocol surfaces may use the more flexible
|
|
148
|
+
top-level structure.
|
|
107
149
|
|
|
108
|
-
## Aliases
|
|
150
|
+
## Field Aliases
|
|
109
151
|
|
|
110
|
-
|
|
111
|
-
or runtime keys.
|
|
152
|
+
Block aliases are published in `Schema` events. Field aliases are presentation
|
|
153
|
+
metadata for tooling. They do not change payload layout or runtime keys.
|
|
112
154
|
|
|
113
155
|
```txt
|
|
114
|
-
maybe #account
|
|
115
|
-
|
|
156
|
+
maybe #account as recipient
|
|
157
|
+
{ uint target, uint resources, #bytes as payload }
|
|
116
158
|
```
|
|
117
159
|
|
|
118
|
-
|
|
160
|
+
Field aliases may be used on any block item, including child blocks and prime
|
|
161
|
+
items.
|
|
119
162
|
|
|
120
|
-
|
|
163
|
+
Child blocks are schema references:
|
|
121
164
|
|
|
122
165
|
```txt
|
|
123
|
-
|
|
166
|
+
{ uint handler, uint resources, bytes32 key, #bytes as witness }
|
|
124
167
|
```
|
|
125
168
|
|
|
126
|
-
Alias resolution is context-dependent. A consumer may resolve `#context` from
|
|
127
|
-
standard
|
|
128
|
-
schema context.
|
|
129
|
-
|
|
130
|
-
|
|
169
|
+
Alias resolution is context-dependent. A consumer may resolve `#context` from
|
|
170
|
+
standard block events, from app-specific block events, or from another active
|
|
171
|
+
schema context. Custom parents should define nested custom blocks from the
|
|
172
|
+
bottom up and reference them by alias. Consumers should reject schemas with
|
|
173
|
+
unresolved aliases. The runtime encoding is still an embedded child block with
|
|
174
|
+
the referenced key and layout.
|
|
131
175
|
|
|
132
176
|
## Field Paths
|
|
133
177
|
|
|
@@ -136,13 +180,13 @@ path does not change the block key, payload bytes, payload length, cursor
|
|
|
136
180
|
behavior, or any onchain validation. It is metadata only.
|
|
137
181
|
|
|
138
182
|
```txt
|
|
139
|
-
|
|
183
|
+
{ uint dst.portal, uint dst.resources, #bytes as dst.payload }
|
|
140
184
|
```
|
|
141
185
|
|
|
142
186
|
This has the same runtime layout as:
|
|
143
187
|
|
|
144
188
|
```txt
|
|
145
|
-
|
|
189
|
+
{ uint portal, uint resources, #bytes as payload }
|
|
146
190
|
```
|
|
147
191
|
|
|
148
192
|
Offchain tooling may decode the dotted form into a nested object:
|
|
@@ -169,11 +213,11 @@ uint dst.portal, uint dst.portal // duplicate path
|
|
|
169
213
|
uint dst, uint dst.portal // prefix/value collision
|
|
170
214
|
```
|
|
171
215
|
|
|
172
|
-
The same rule applies to
|
|
216
|
+
The same rule applies to field aliases:
|
|
173
217
|
|
|
174
218
|
```txt
|
|
175
|
-
|
|
176
|
-
maybe #account
|
|
219
|
+
{ uint target, uint resources, #bytes as calldata.payload }
|
|
220
|
+
maybe #account as recipient.account
|
|
177
221
|
```
|
|
178
222
|
|
|
179
223
|
## Field Types
|
|
@@ -226,8 +270,8 @@ the layout of an ID only apply to structured IDs.
|
|
|
226
270
|
|
|
227
271
|
## Identifiers
|
|
228
272
|
|
|
229
|
-
Block
|
|
230
|
-
one or more lower camelCase path segments separated by dots:
|
|
273
|
+
Block aliases use lower camelCase ASCII identifiers. Field names and aliases
|
|
274
|
+
use one or more lower camelCase path segments separated by dots:
|
|
231
275
|
|
|
232
276
|
```txt
|
|
233
277
|
[a-z][a-zA-Z0-9]*
|
|
@@ -246,48 +290,42 @@ asset.
|
|
|
246
290
|
```
|
|
247
291
|
|
|
248
292
|
Reserved words include `maybe`, `many`, `as`, all field type names, and the
|
|
249
|
-
reserved block
|
|
250
|
-
|
|
293
|
+
reserved block aliases `bytes` and `list`. For dotted paths, reserved words are
|
|
294
|
+
invalid in any path segment.
|
|
251
295
|
|
|
252
296
|
## Reserved Blocks
|
|
253
297
|
|
|
254
298
|
- `#bytes`: raw dynamic bytes, written without a body
|
|
255
|
-
- `#
|
|
299
|
+
- `#string`: UTF-8 string bytes, written without a body
|
|
256
300
|
- `#list`: generic list wrapper emitted by `many`
|
|
257
301
|
|
|
258
|
-
|
|
302
|
+
Custom input shapes should define their own context-local block key and publish
|
|
303
|
+
that key with a `Schema` event:
|
|
259
304
|
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
If a schema string starts with a fixed field type, it is shorthand for one
|
|
266
|
-
top-level `#data` block:
|
|
267
|
-
|
|
268
|
-
```txt
|
|
269
|
-
uint foo, bytes32 tag
|
|
305
|
+
```solidity
|
|
306
|
+
bytes4 constant Input = Keys.Local;
|
|
307
|
+
emit Schema(host, Input, "{ bytes32 asset, uint amount }", bytes32("payment"));
|
|
270
308
|
```
|
|
271
309
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
```
|
|
310
|
+
Use `Keys.local(n)` when a host needs more than one local block key. The key can
|
|
311
|
+
also be a small literal, a selector, or any other `bytes4` value that is unique
|
|
312
|
+
in the context where it is used. The alias names the block; the schema string
|
|
313
|
+
describes only the payload body.
|
|
277
314
|
|
|
278
315
|
## Standard Blocks
|
|
279
316
|
|
|
280
317
|
Common protocol schemas live in `contracts/blocks/Schema.sol`:
|
|
281
318
|
|
|
282
319
|
```txt
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
320
|
+
amount { bytes32 asset, uint amount }
|
|
321
|
+
balance { bytes32 asset, uint amount }
|
|
322
|
+
custody { uint host, bytes32 asset, uint amount }
|
|
323
|
+
call { uint target, uint resources, #bytes as payload }
|
|
324
|
+
step { uint target, uint resources, #bytes as request }
|
|
325
|
+
context { bytes32 account, #bytes as state, #bytes as request }
|
|
326
|
+
recover { uint handler, uint resources, bytes32 key, #bytes as witness }
|
|
327
|
+
auth { uint cid, uint deadline, #bytes as proof }
|
|
328
|
+
schema { bytes4 key, #string as body, bytes32 name }
|
|
291
329
|
```
|
|
292
330
|
|
|
293
|
-
`Keys.sol` contains the corresponding runtime keys.
|
|
331
|
+
`Keys.sol` contains the corresponding standard runtime keys.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
|
+
pragma solidity ^0.8.33;
|
|
3
|
+
|
|
4
|
+
import {EventEmitter} from "./Emitter.sol";
|
|
5
|
+
|
|
6
|
+
/// @title EndpointEvent
|
|
7
|
+
/// @notice Emitted during host deployment to publish a callable endpoint descriptor.
|
|
8
|
+
abstract contract EndpointEvent is EventEmitter {
|
|
9
|
+
string private constant ABI = "event Endpoint(uint indexed host, uint id, bytes32 descriptor)";
|
|
10
|
+
|
|
11
|
+
/// @param host Host node ID that exposes the endpoint.
|
|
12
|
+
/// @param id Endpoint node ID.
|
|
13
|
+
/// @param descriptor Packed endpoint lane metadata and flags.
|
|
14
|
+
event Endpoint(uint indexed host, uint id, bytes32 descriptor);
|
|
15
|
+
|
|
16
|
+
constructor() {
|
|
17
|
+
emit EventAbi(ABI);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
|
+
pragma solidity ^0.8.33;
|
|
3
|
+
|
|
4
|
+
import {EventEmitter} from "./Emitter.sol";
|
|
5
|
+
|
|
6
|
+
/// @title SchemaEvent
|
|
7
|
+
/// @notice Emitted during host deployment to publish a block key and payload schema.
|
|
8
|
+
/// Block keys are opaque `bytes4` tags. Standard protocol blocks use
|
|
9
|
+
/// keccak-derived keys by convention, but custom block keys only need to be
|
|
10
|
+
/// unique within the publishing host/schema context.
|
|
11
|
+
abstract contract SchemaEvent is EventEmitter {
|
|
12
|
+
string private constant ABI = "event Schema(uint indexed host, bytes4 key, string schema, bytes32 name)";
|
|
13
|
+
|
|
14
|
+
/// @param host Host node ID that publishes this block schema.
|
|
15
|
+
/// @param key Block type key being defined by `host`.
|
|
16
|
+
/// @param schema Schema DSL string describing the block payload body.
|
|
17
|
+
/// @param name Optional block alias used by endpoint descriptors and nested schemas.
|
|
18
|
+
event Schema(uint indexed host, bytes4 key, string schema, bytes32 name);
|
|
19
|
+
|
|
20
|
+
constructor() {
|
|
21
|
+
emit EventAbi(ABI);
|
|
22
|
+
}
|
|
23
|
+
}
|
package/guards/Base.sol
CHANGED
|
@@ -2,24 +2,33 @@
|
|
|
2
2
|
pragma solidity ^0.8.33;
|
|
3
3
|
|
|
4
4
|
import {AccessControl} from "../core/Access.sol";
|
|
5
|
-
import {
|
|
6
|
-
import {LabeledEvent} from "../events/Labeled.sol";
|
|
5
|
+
import {EndpointBase} from "../core/Endpoint.sol";
|
|
7
6
|
import {Nodes} from "../utils/Nodes.sol";
|
|
7
|
+
import {Selectors} from "../utils/Selectors.sol";
|
|
8
8
|
|
|
9
9
|
/// @title GuardBase
|
|
10
10
|
/// @notice Abstract base for guardian-only direct host actions.
|
|
11
11
|
/// Guard actions are non-payable direct calls with no command context, state, or response.
|
|
12
|
-
abstract contract GuardBase is AccessControl,
|
|
12
|
+
abstract contract GuardBase is AccessControl, EndpointBase {
|
|
13
13
|
/// @dev Restrict execution to active guardian addresses.
|
|
14
14
|
modifier onlyGuardian() {
|
|
15
15
|
if (!isGuardian(msg.sender)) revert AccessDenied();
|
|
16
16
|
_;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
/// @notice
|
|
20
|
-
/// @param
|
|
21
|
-
/// @
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
/// @notice Publish guard metadata and a default label.
|
|
20
|
+
/// @param name Default human-readable guard label and selector name.
|
|
21
|
+
/// @param input Packed input lane plus optional group byte.
|
|
22
|
+
/// @param selector Guard ABI selector, or zero to derive it from `name`.
|
|
23
|
+
/// @return id Guard action node ID.
|
|
24
|
+
/// @return descriptor Packed endpoint lane metadata and flags.
|
|
25
|
+
function guard(
|
|
26
|
+
string memory name,
|
|
27
|
+
bytes9 input,
|
|
28
|
+
bytes4 selector
|
|
29
|
+
) internal returns (uint id, bytes32 descriptor) {
|
|
30
|
+
selector = selector == bytes4(0) ? Selectors.guard(name) : selector;
|
|
31
|
+
id = Nodes.toGuard(selector, address(this));
|
|
32
|
+
descriptor = endpoint(id, name, bytes9(0), input, bytes9(0), false, false);
|
|
24
33
|
}
|
|
25
34
|
}
|
package/guards/Revoke.sol
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
pragma solidity ^0.8.33;
|
|
3
3
|
|
|
4
4
|
import {GuardBase} from "./Base.sol";
|
|
5
|
-
import {Cursors, Cur,
|
|
5
|
+
import {Cursors, Cur, Keys} from "../Cursors.sol";
|
|
6
6
|
using Cursors for Cur;
|
|
7
7
|
|
|
8
8
|
/// @title Revoke
|
|
@@ -10,21 +10,19 @@ using Cursors for Cur;
|
|
|
10
10
|
/// Each NODE block in the request is deauthorized on the host.
|
|
11
11
|
/// Only callable by active guardian addresses.
|
|
12
12
|
abstract contract Revoke is GuardBase {
|
|
13
|
-
|
|
13
|
+
bytes32 private immutable descriptor;
|
|
14
14
|
|
|
15
15
|
constructor() {
|
|
16
|
-
|
|
17
|
-
emit Labeled(revokeId, bytes32(0), "revoke");
|
|
16
|
+
(, descriptor) = guard("revoke", Keys.Node, 0);
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
function revoke(bytes calldata request) external onlyGuardian {
|
|
21
|
-
(Cur memory input, ) =
|
|
20
|
+
(Cur memory input, ) = openInput(request, descriptor);
|
|
22
21
|
|
|
23
22
|
while (input.i < input.len) {
|
|
24
23
|
uint node = input.unpackNode();
|
|
25
24
|
setNode(node, false);
|
|
26
25
|
}
|
|
27
26
|
|
|
28
|
-
input.complete();
|
|
29
27
|
}
|
|
30
28
|
}
|