@rootzero/contracts 0.9.2 → 0.9.4
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/Commands.sol +4 -3
- package/Core.sol +3 -0
- package/Cursors.sol +1 -1
- package/README.md +18 -24
- package/blocks/Cursors.sol +332 -335
- package/blocks/Keys.sol +38 -57
- package/blocks/Schema.sol +55 -114
- package/blocks/Writers.sol +361 -255
- package/commands/Base.sol +6 -48
- package/commands/Burn.sol +4 -4
- package/commands/Credit.sol +5 -4
- package/commands/Debit.sol +6 -5
- package/commands/Deposit.sol +17 -14
- package/commands/Provision.sol +17 -14
- package/commands/Transfer.sol +4 -4
- package/commands/Withdraw.sol +5 -4
- package/commands/admin/AllowAssets.sol +3 -3
- package/commands/admin/Allowance.sol +3 -3
- package/commands/admin/Authorize.sol +3 -3
- package/commands/admin/DenyAssets.sol +3 -3
- package/commands/admin/Destroy.sol +1 -1
- package/commands/admin/Execute.sol +9 -8
- package/commands/admin/Init.sol +1 -1
- package/commands/admin/Unauthorize.sol +3 -3
- package/core/Access.sol +11 -0
- package/core/Context.sol +11 -13
- package/core/Payable.sol +57 -0
- package/core/Pipeline.sol +55 -0
- package/docs/Schema.md +194 -0
- package/events/Admin.sol +5 -1
- package/events/Command.sol +6 -2
- package/events/Listing.sol +3 -4
- package/events/Peer.sol +5 -3
- package/events/Query.sol +5 -2
- package/package.json +2 -2
- package/peer/AllowAssets.sol +3 -3
- package/peer/Allowance.sol +3 -3
- package/peer/BalancePull.sol +43 -0
- package/peer/DenyAssets.sol +3 -3
- package/peer/Pipe.sol +38 -0
- package/peer/Settle.sol +3 -3
- package/queries/Assets.sol +7 -6
- package/queries/Balances.sol +5 -4
- package/queries/Positions.sol +14 -14
- package/utils/Value.sol +8 -14
- package/commands/Pipe.sol +0 -67
- package/docs/GETTING_STARTED.md +0 -294
- package/peer/AssetPull.sol +0 -43
package/core/Payable.sol
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
|
+
pragma solidity ^0.8.33;
|
|
3
|
+
|
|
4
|
+
import {Budget, Values} from "../utils/Value.sol";
|
|
5
|
+
|
|
6
|
+
/// @title Payable
|
|
7
|
+
/// @notice Abstract mixin for entrypoints that accept native value (`msg.value`).
|
|
8
|
+
/// Provides a shared settlement hook for any unspent value remaining in the
|
|
9
|
+
/// mutable budget after execution completes.
|
|
10
|
+
abstract contract Payable {
|
|
11
|
+
/// @dev Thrown when a payable entrypoint completes with unspent native value.
|
|
12
|
+
/// Override `settleValue` to implement refund or forwarding behavior instead.
|
|
13
|
+
error UnusedValue(uint remaining);
|
|
14
|
+
|
|
15
|
+
/// @notice Create a native-value budget from the current call's `msg.value`.
|
|
16
|
+
/// @return Budget initialised with the full `msg.value`.
|
|
17
|
+
function valueBudget() internal view returns (Budget memory) {
|
|
18
|
+
return Budget({remaining: msg.value});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/// @notice Deduct `amount` from the budget and return it.
|
|
22
|
+
/// @param budget Mutable budget to deduct from.
|
|
23
|
+
/// @param amount Native value to spend.
|
|
24
|
+
/// @return The same `amount`, ready to forward to a callee.
|
|
25
|
+
function useValue(Budget memory budget, uint amount) internal pure returns (uint) {
|
|
26
|
+
return Values.use(budget, amount);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// @notice Deduct `amount` from the budget and return it as a new sub-budget.
|
|
30
|
+
/// @param budget Mutable parent budget to deduct from.
|
|
31
|
+
/// @param amount Native value to assign to the sub-budget.
|
|
32
|
+
/// @return A new budget with `amount` remaining.
|
|
33
|
+
function allocateValue(Budget memory budget, uint amount) internal pure returns (Budget memory) {
|
|
34
|
+
return Values.allocate(budget, amount);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/// @notice Drains the budget and settles any remaining native value.
|
|
38
|
+
/// @dev Calls the amount-based `settleValue` hook only when some value remains.
|
|
39
|
+
/// @param account Account identifier for the current invocation.
|
|
40
|
+
/// @param budget Mutable native-value budget used during execution.
|
|
41
|
+
function settleValue(bytes32 account, Budget memory budget) internal {
|
|
42
|
+
uint value = budget.remaining;
|
|
43
|
+
if (value == 0) return;
|
|
44
|
+
budget.remaining = 0;
|
|
45
|
+
settleValue(account, value);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// @notice Handles leftover native value after payable execution has finished.
|
|
49
|
+
/// @dev Override this hook to refund or redirect unused value.
|
|
50
|
+
/// The default implementation rejects any leftover amount.
|
|
51
|
+
/// @param account Account identifier for the current invocation.
|
|
52
|
+
/// @param remaining Unspent native value left in the budget, in wei.
|
|
53
|
+
function settleValue(bytes32 account, uint remaining) internal virtual {
|
|
54
|
+
account;
|
|
55
|
+
revert UnusedValue(remaining);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
|
+
pragma solidity ^0.8.33;
|
|
3
|
+
|
|
4
|
+
import {Cursors, Cur} from "../Cursors.sol";
|
|
5
|
+
import {Payable} from "./Payable.sol";
|
|
6
|
+
import {Budget} from "../utils/Value.sol";
|
|
7
|
+
|
|
8
|
+
using Cursors for Cur;
|
|
9
|
+
|
|
10
|
+
/// @title Pipeline
|
|
11
|
+
/// @notice Core pipeline functionality shared by higher-level surfaces.
|
|
12
|
+
abstract contract Pipeline is Payable {
|
|
13
|
+
error UnexpectedState();
|
|
14
|
+
|
|
15
|
+
/// @notice Override to dispatch one piped step.
|
|
16
|
+
/// Called once per STEP block. The returned bytes become the state passed to
|
|
17
|
+
/// the next step, and the final returned state must be empty.
|
|
18
|
+
/// @param target Node ID to invoke or handle.
|
|
19
|
+
/// @param account Account identifier for the piped context.
|
|
20
|
+
/// @param state Current threaded state block stream.
|
|
21
|
+
/// @param request Step request block stream.
|
|
22
|
+
/// @param value Native value assigned to this step.
|
|
23
|
+
/// @return Updated state block stream for the next step.
|
|
24
|
+
function dispatch(
|
|
25
|
+
uint target,
|
|
26
|
+
bytes32 account,
|
|
27
|
+
bytes memory state,
|
|
28
|
+
bytes calldata request,
|
|
29
|
+
uint value
|
|
30
|
+
) internal virtual returns (bytes memory);
|
|
31
|
+
|
|
32
|
+
/// @notice Execute a STEP block stream through the pipeline.
|
|
33
|
+
/// @dev Reverts with `UnexpectedState` if the final threaded state is non-empty.
|
|
34
|
+
/// @param account Account identifier used for each dispatched step.
|
|
35
|
+
/// @param state Initial state block stream passed to the first step.
|
|
36
|
+
/// @param steps STEP block stream to execute.
|
|
37
|
+
/// @param budget Mutable native-value budget shared across all steps.
|
|
38
|
+
function pipe(
|
|
39
|
+
bytes32 account,
|
|
40
|
+
bytes memory state,
|
|
41
|
+
bytes calldata steps,
|
|
42
|
+
Budget memory budget
|
|
43
|
+
) internal {
|
|
44
|
+
(Cur memory input, ) = Cursors.init(steps, 1);
|
|
45
|
+
|
|
46
|
+
while (input.i < input.bound) {
|
|
47
|
+
(uint target, uint value, bytes calldata request) = input.unpackStep();
|
|
48
|
+
state = dispatch(target, account, state, request, useValue(budget, value));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (state.length != 0) revert UnexpectedState();
|
|
52
|
+
settleValue(account, budget);
|
|
53
|
+
input.close();
|
|
54
|
+
}
|
|
55
|
+
}
|
package/docs/Schema.md
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# Schema
|
|
2
|
+
|
|
3
|
+
Rootzero request and response data is encoded as a stream of typed blocks. A
|
|
4
|
+
schema string describes payload layout for discovery events and tooling; the
|
|
5
|
+
runtime block key is derived only from the block name.
|
|
6
|
+
|
|
7
|
+
## Wire Format
|
|
8
|
+
|
|
9
|
+
Every block uses the same header:
|
|
10
|
+
|
|
11
|
+
```txt
|
|
12
|
+
[bytes4 key][uint32 payloadLen][payload]
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`payloadLen` is big-endian and counts only payload bytes. Child blocks and list
|
|
16
|
+
items use the same header format.
|
|
17
|
+
|
|
18
|
+
The block key is:
|
|
19
|
+
|
|
20
|
+
```txt
|
|
21
|
+
bytes4(keccak256("#name"))
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
For example, `#amount { bytes32 asset, bytes32 meta, uint amount }` uses the key
|
|
25
|
+
derived from `#amount`. Blocks must not be overloaded: one block name should have
|
|
26
|
+
one protocol meaning.
|
|
27
|
+
|
|
28
|
+
## Block Syntax
|
|
29
|
+
|
|
30
|
+
A block starts with `#`. Fixed fields are written in braces:
|
|
31
|
+
|
|
32
|
+
```txt
|
|
33
|
+
#amount { bytes32 asset, bytes32 meta, uint amount }
|
|
34
|
+
#account { bytes32 account }
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
A block without braces has no payload:
|
|
38
|
+
|
|
39
|
+
```txt
|
|
40
|
+
#unit
|
|
41
|
+
#bytes
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Empty braces are invalid. A zero-payload block must omit braces.
|
|
45
|
+
|
|
46
|
+
A schema is a comma-separated list of items. Order is significant.
|
|
47
|
+
|
|
48
|
+
```txt
|
|
49
|
+
#amount { bytes32 asset, bytes32 meta, uint amount },
|
|
50
|
+
maybe #account { bytes32 account }
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Payload Layout
|
|
54
|
+
|
|
55
|
+
A block payload has fixed fields first, followed by an optional child-block tail.
|
|
56
|
+
Once a child block appears, no more fixed fields may follow.
|
|
57
|
+
|
|
58
|
+
```txt
|
|
59
|
+
#call { uint target, uint value, #bytes as payload }
|
|
60
|
+
#context { bytes32 account, uint value, #bytes as state, #bytes as request }
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The tail is embedded directly as child block bytes. There is no wrapper around a
|
|
64
|
+
child-block tail.
|
|
65
|
+
|
|
66
|
+
Raw dynamic bytes are represented with the reserved `#bytes` child block. Use an
|
|
67
|
+
alias to give those bytes a presentation name:
|
|
68
|
+
|
|
69
|
+
```txt
|
|
70
|
+
#bytes as payload
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Modifiers
|
|
74
|
+
|
|
75
|
+
Cardinality is expressed with prefix keywords:
|
|
76
|
+
|
|
77
|
+
```txt
|
|
78
|
+
#balance { bytes32 asset, bytes32 meta, uint amount }
|
|
79
|
+
maybe #balance { bytes32 asset, bytes32 meta, uint amount }
|
|
80
|
+
many #balance { bytes32 asset, bytes32 meta, uint amount }
|
|
81
|
+
maybe many #balance { bytes32 asset, bytes32 meta, uint amount }
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
- no prefix: one required item
|
|
85
|
+
- `maybe`: optional item
|
|
86
|
+
- `many`: one `#list` block whose payload contains repeated items
|
|
87
|
+
- `maybe many`: optional `#list` block
|
|
88
|
+
|
|
89
|
+
`maybe` emits no placeholder when absent. `many` wraps repeated items in one
|
|
90
|
+
generic list block; it does not repeat the item in place.
|
|
91
|
+
|
|
92
|
+
## Prime Items
|
|
93
|
+
|
|
94
|
+
The empty string `""` means no schema. Whitespace-only schemas are invalid.
|
|
95
|
+
|
|
96
|
+
For a non-empty schema, the first top-level item is the prime item. Prime items
|
|
97
|
+
may repeat at the top level for batching. Later top-level items are globals for
|
|
98
|
+
the whole batch and are not counted as per-operation prime blocks.
|
|
99
|
+
|
|
100
|
+
The prime item cannot be optional. If a command needs a per-operation marker with
|
|
101
|
+
no payload, use a zero-payload block such as `#unit`.
|
|
102
|
+
|
|
103
|
+
## Aliases
|
|
104
|
+
|
|
105
|
+
Aliases are presentation metadata for tooling. They do not change payload layout
|
|
106
|
+
or runtime keys.
|
|
107
|
+
|
|
108
|
+
```txt
|
|
109
|
+
maybe #account { bytes32 account } as recipient
|
|
110
|
+
#call { uint target, uint value, #bytes as payload }
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Aliases may be used on any block item, including child blocks and prime items.
|
|
114
|
+
|
|
115
|
+
## Field Types
|
|
116
|
+
|
|
117
|
+
Supported field types are chain-neutral:
|
|
118
|
+
|
|
119
|
+
```txt
|
|
120
|
+
uint, uint8, uint16, uint32, uint64, uint128, uint256
|
|
121
|
+
int, int8, int16, int32, int64, int128, int256
|
|
122
|
+
bool
|
|
123
|
+
bytes1 through bytes32
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`uint` means `uint256`; `int` means `int256`. Other integer widths, unsized
|
|
127
|
+
`bytes`, `string`, and array syntax are not part of the core schema DSL.
|
|
128
|
+
|
|
129
|
+
Integers are encoded big-endian. Signed integers use two's-complement encoding
|
|
130
|
+
for their declared width. `bool` is one byte: `0x00` for false and `0x01` for
|
|
131
|
+
true. `bytesN` values are encoded as exactly `N` bytes with no padding.
|
|
132
|
+
|
|
133
|
+
## Identifiers
|
|
134
|
+
|
|
135
|
+
Block names, field names, and aliases use lower camelCase ASCII identifiers:
|
|
136
|
+
|
|
137
|
+
```txt
|
|
138
|
+
[a-z][a-zA-Z0-9]*
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Invalid examples:
|
|
142
|
+
|
|
143
|
+
```txt
|
|
144
|
+
Amount
|
|
145
|
+
asset_meta
|
|
146
|
+
asset-meta
|
|
147
|
+
0account
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Reserved words include `maybe`, `many`, `as`, all field type names, and the
|
|
151
|
+
reserved block names `bytes`, `data`, and `list`.
|
|
152
|
+
|
|
153
|
+
## Reserved Blocks
|
|
154
|
+
|
|
155
|
+
- `#bytes`: raw dynamic bytes, written without a body
|
|
156
|
+
- `#data`: generic/custom payload block
|
|
157
|
+
- `#list`: generic list wrapper emitted by `many`
|
|
158
|
+
|
|
159
|
+
Use `#data` when a local schema needs a stable generic key:
|
|
160
|
+
|
|
161
|
+
```txt
|
|
162
|
+
#data { uint foo, bytes32 tag }
|
|
163
|
+
#data { #bytes as payload }
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
If a schema string starts with a fixed field type, it is shorthand for one
|
|
167
|
+
top-level `#data` block:
|
|
168
|
+
|
|
169
|
+
```txt
|
|
170
|
+
uint foo, bytes32 tag
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
expands to:
|
|
174
|
+
|
|
175
|
+
```txt
|
|
176
|
+
#data { uint foo, bytes32 tag }
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Standard Blocks
|
|
180
|
+
|
|
181
|
+
Common protocol schemas live in `contracts/blocks/Schema.sol`:
|
|
182
|
+
|
|
183
|
+
```txt
|
|
184
|
+
#amount { bytes32 asset, bytes32 meta, uint amount }
|
|
185
|
+
#balance { bytes32 asset, bytes32 meta, uint amount }
|
|
186
|
+
#custody { uint host, bytes32 asset, bytes32 meta, uint amount }
|
|
187
|
+
#payout { bytes32 account, bytes32 asset, bytes32 meta, uint amount }
|
|
188
|
+
#call { uint target, uint value, #bytes as payload }
|
|
189
|
+
#step { uint target, uint value, #bytes as request }
|
|
190
|
+
#context { bytes32 account, uint value, #bytes as state, #bytes as request }
|
|
191
|
+
#auth { uint cid, uint deadline, #bytes as proof }
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
`Keys.sol` contains the corresponding runtime keys.
|
package/events/Admin.sol
CHANGED
|
@@ -4,13 +4,16 @@ pragma solidity ^0.8.33;
|
|
|
4
4
|
import { EventEmitter } from "./Emitter.sol";
|
|
5
5
|
|
|
6
6
|
string constant ABI =
|
|
7
|
-
"event Admin(uint indexed host, uint id, string name, string request, bytes4 state, bytes4 output, bool acceptsValue)";
|
|
7
|
+
"event Admin(uint indexed host, uint id, string name, bytes32 shape, string request, bytes4 state, bytes4 output, bool acceptsValue)";
|
|
8
8
|
|
|
9
9
|
/// @notice Emitted once per admin command during host deployment to publish its request schema and state keys.
|
|
10
10
|
abstract contract AdminEvent is EventEmitter {
|
|
11
11
|
/// @param host Host node ID that owns this admin command.
|
|
12
12
|
/// @param id Command node ID.
|
|
13
13
|
/// @param name Human-readable command name.
|
|
14
|
+
/// @param shape Per-operation prime block counts encoded as `request:state:output`.
|
|
15
|
+
/// Blocks outside the prime runs are global batch blocks and are excluded
|
|
16
|
+
/// from the counts.
|
|
14
17
|
/// @param request Schema DSL string describing the request shape.
|
|
15
18
|
/// @param state Block key expected for input state, or `Keys.Empty`.
|
|
16
19
|
/// @param output Block key produced for output state, or `Keys.Empty`.
|
|
@@ -19,6 +22,7 @@ abstract contract AdminEvent is EventEmitter {
|
|
|
19
22
|
uint indexed host,
|
|
20
23
|
uint id,
|
|
21
24
|
string name,
|
|
25
|
+
bytes32 shape,
|
|
22
26
|
string request,
|
|
23
27
|
bytes4 state,
|
|
24
28
|
bytes4 output,
|
package/events/Command.sol
CHANGED
|
@@ -4,14 +4,17 @@ pragma solidity ^0.8.33;
|
|
|
4
4
|
import { EventEmitter } from "./Emitter.sol";
|
|
5
5
|
|
|
6
6
|
string constant ABI =
|
|
7
|
-
"event Command(uint indexed host, uint id, string name, string request, bytes4 state, bytes4 output, bool acceptsValue)";
|
|
7
|
+
"event Command(uint indexed host, uint id, string name, bytes32 shape, string request, bytes4 state, bytes4 output, bool acceptsValue)";
|
|
8
8
|
|
|
9
9
|
/// @notice Emitted once per command during host deployment to publish its request schema and state keys.
|
|
10
10
|
abstract contract CommandEvent is EventEmitter {
|
|
11
11
|
/// @param host Host node ID that owns this command.
|
|
12
12
|
/// @param id Command node ID.
|
|
13
13
|
/// @param name Human-readable command name.
|
|
14
|
-
/// @param
|
|
14
|
+
/// @param shape Per-operation prime block counts encoded as `request:state:output`.
|
|
15
|
+
/// Blocks outside the prime runs are global batch blocks and are excluded
|
|
16
|
+
/// from the counts.
|
|
17
|
+
/// @param request Schema string describing the request shape.
|
|
15
18
|
/// @param state Block key expected for input state, or `Keys.Empty`.
|
|
16
19
|
/// @param output Block key produced for output state, or `Keys.Empty`.
|
|
17
20
|
/// @param acceptsValue Whether the command entrypoint accepts nonzero `msg.value`.
|
|
@@ -19,6 +22,7 @@ abstract contract CommandEvent is EventEmitter {
|
|
|
19
22
|
uint indexed host,
|
|
20
23
|
uint id,
|
|
21
24
|
string name,
|
|
25
|
+
bytes32 shape,
|
|
22
26
|
string request,
|
|
23
27
|
bytes4 state,
|
|
24
28
|
bytes4 output,
|
package/events/Listing.sol
CHANGED
|
@@ -3,16 +3,15 @@ pragma solidity ^0.8.33;
|
|
|
3
3
|
|
|
4
4
|
import { EventEmitter } from "./Emitter.sol";
|
|
5
5
|
|
|
6
|
-
string constant ABI = "event Listing(uint indexed host, bytes32 asset, bytes32 meta, bool active
|
|
6
|
+
string constant ABI = "event Listing(uint indexed host, bytes32 asset, bytes32 meta, bool active)";
|
|
7
7
|
|
|
8
|
-
/// @notice Emitted when an asset listing is
|
|
8
|
+
/// @notice Emitted when an asset listing is updated on a host.
|
|
9
9
|
abstract contract ListingEvent is EventEmitter {
|
|
10
10
|
/// @param host Host node ID that manages this listing.
|
|
11
11
|
/// @param asset Asset identifier.
|
|
12
12
|
/// @param meta Asset metadata slot.
|
|
13
13
|
/// @param active True if the listing is currently active.
|
|
14
|
-
|
|
15
|
-
event Listing(uint indexed host, bytes32 asset, bytes32 meta, bool active, bool created);
|
|
14
|
+
event Listing(uint indexed host, bytes32 asset, bytes32 meta, bool active);
|
|
16
15
|
|
|
17
16
|
constructor() {
|
|
18
17
|
emit EventAbi(ABI);
|
package/events/Peer.sol
CHANGED
|
@@ -4,16 +4,18 @@ pragma solidity ^0.8.33;
|
|
|
4
4
|
import { EventEmitter } from "./Emitter.sol";
|
|
5
5
|
|
|
6
6
|
string constant ABI =
|
|
7
|
-
"event Peer(uint indexed host, uint id, string name, string request, bool acceptsValue)";
|
|
7
|
+
"event Peer(uint indexed host, uint id, string name, bytes32 shape, string request, string response, bool acceptsValue)";
|
|
8
8
|
|
|
9
|
-
/// @notice Emitted once per peer during host deployment to publish its request
|
|
9
|
+
/// @notice Emitted once per peer during host deployment to publish its request and response schemas.
|
|
10
10
|
abstract contract PeerEvent is EventEmitter {
|
|
11
11
|
/// @param host Host node ID that owns this peer.
|
|
12
12
|
/// @param id Peer node ID.
|
|
13
13
|
/// @param name Human-readable peer name.
|
|
14
|
+
/// @param shape Prime block counts as `request:response`; global blocks are excluded.
|
|
14
15
|
/// @param request Schema DSL string describing the peer request shape.
|
|
16
|
+
/// @param response Schema DSL string describing the peer response shape.
|
|
15
17
|
/// @param acceptsValue Whether the peer entrypoint accepts nonzero `msg.value`.
|
|
16
|
-
event Peer(uint indexed host, uint id, string name, string request, bool acceptsValue);
|
|
18
|
+
event Peer(uint indexed host, uint id, string name, bytes32 shape, string request, string response, bool acceptsValue);
|
|
17
19
|
|
|
18
20
|
constructor() {
|
|
19
21
|
emit EventAbi(ABI);
|
package/events/Query.sol
CHANGED
|
@@ -3,16 +3,19 @@ pragma solidity ^0.8.33;
|
|
|
3
3
|
|
|
4
4
|
import {EventEmitter} from "./Emitter.sol";
|
|
5
5
|
|
|
6
|
-
string constant ABI = "event Query(uint indexed host, uint id, string name, string request, string response)";
|
|
6
|
+
string constant ABI = "event Query(uint indexed host, uint id, string name, bytes32 shape, string request, string response)";
|
|
7
7
|
|
|
8
8
|
/// @notice Emitted once per query during host deployment to publish its request and response schemas.
|
|
9
9
|
abstract contract QueryEvent is EventEmitter {
|
|
10
10
|
/// @param host Host node ID that owns this query.
|
|
11
11
|
/// @param id Query node ID.
|
|
12
12
|
/// @param name Human-readable query name.
|
|
13
|
+
/// @param shape Per-operation prime block counts encoded as `request:response`.
|
|
14
|
+
/// Blocks outside the prime runs are global batch blocks and are excluded
|
|
15
|
+
/// from the counts.
|
|
13
16
|
/// @param request Schema DSL string describing the query request shape.
|
|
14
17
|
/// @param response Schema DSL string describing the query response shape.
|
|
15
|
-
event Query(uint indexed host, uint id, string name, string request, string response);
|
|
18
|
+
event Query(uint indexed host, uint id, string name, bytes32 shape, string request, string response);
|
|
16
19
|
|
|
17
20
|
constructor() {
|
|
18
21
|
emit EventAbi(ABI);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rootzero/contracts",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.4",
|
|
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,7 +9,7 @@
|
|
|
9
9
|
"**/*.sol",
|
|
10
10
|
"README.md",
|
|
11
11
|
"LICENSE",
|
|
12
|
-
"docs/
|
|
12
|
+
"docs/Schema.md"
|
|
13
13
|
],
|
|
14
14
|
"publishConfig": {
|
|
15
15
|
"access": "public"
|
package/peer/AllowAssets.sol
CHANGED
|
@@ -15,19 +15,19 @@ abstract contract PeerAllowAssets is PeerBase, AllowAssetsHook {
|
|
|
15
15
|
uint internal immutable peerAllowAssetsId = peerId(NAME);
|
|
16
16
|
|
|
17
17
|
constructor() {
|
|
18
|
-
emit Peer(host, peerAllowAssetsId, NAME, Schemas.Asset, false);
|
|
18
|
+
emit Peer(host, peerAllowAssetsId, NAME, "1:0", Schemas.Asset, "", false);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/// @notice Execute the allow-assets peer call.
|
|
22
22
|
function peerAllowAssets(bytes calldata request) external onlyPeer returns (bytes memory) {
|
|
23
|
-
(Cur memory assets,
|
|
23
|
+
(Cur memory assets, ) = cursor(request, 1);
|
|
24
24
|
|
|
25
25
|
while (assets.i < assets.bound) {
|
|
26
26
|
(bytes32 asset, bytes32 meta) = assets.unpackAsset();
|
|
27
27
|
allowAsset(asset, meta);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
assets.
|
|
30
|
+
assets.close();
|
|
31
31
|
return "";
|
|
32
32
|
}
|
|
33
33
|
}
|
package/peer/Allowance.sol
CHANGED
|
@@ -16,12 +16,12 @@ abstract contract PeerAllowance is PeerBase, AllowanceHook {
|
|
|
16
16
|
uint internal immutable peerAllowanceId = peerId(NAME);
|
|
17
17
|
|
|
18
18
|
constructor() {
|
|
19
|
-
emit Peer(host, peerAllowanceId, NAME, Schemas.Amount, false);
|
|
19
|
+
emit Peer(host, peerAllowanceId, NAME, "1:0", Schemas.Amount, "", false);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
/// @notice Execute the allowance peer call.
|
|
23
23
|
function peerAllowance(bytes calldata request) external onlyPeer returns (bytes memory) {
|
|
24
|
-
(Cur memory amounts,
|
|
24
|
+
(Cur memory amounts, ) = cursor(request, 1);
|
|
25
25
|
uint peer = caller();
|
|
26
26
|
|
|
27
27
|
while (amounts.i < amounts.bound) {
|
|
@@ -29,7 +29,7 @@ abstract contract PeerAllowance is PeerBase, AllowanceHook {
|
|
|
29
29
|
allowance(peer, asset, meta, amount);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
amounts.
|
|
32
|
+
amounts.close();
|
|
33
33
|
return "";
|
|
34
34
|
}
|
|
35
35
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
|
+
pragma solidity ^0.8.33;
|
|
3
|
+
|
|
4
|
+
import {PeerBase} from "./Base.sol";
|
|
5
|
+
import {Cursors, Cur, Schemas} from "../Cursors.sol";
|
|
6
|
+
|
|
7
|
+
using Cursors for Cur;
|
|
8
|
+
|
|
9
|
+
abstract contract BalancePullHook {
|
|
10
|
+
/// @notice Override to process one incoming balance-based pull request from a peer host.
|
|
11
|
+
/// @param peer Peer host node ID for this request.
|
|
12
|
+
/// @param asset Requested asset identifier.
|
|
13
|
+
/// @param meta Requested asset metadata slot.
|
|
14
|
+
/// @param amount Requested amount in the asset's native units.
|
|
15
|
+
function balancePull(uint peer, bytes32 asset, bytes32 meta, uint amount) internal virtual;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/// @title PeerBalancePull
|
|
19
|
+
/// @notice Peer that pulls requested balances from a peer host into this one.
|
|
20
|
+
/// Each BALANCE block in the request calls `balancePull(peer, asset, meta, amount)`.
|
|
21
|
+
/// Restricted to trusted peers.
|
|
22
|
+
abstract contract PeerBalancePull is PeerBase, BalancePullHook {
|
|
23
|
+
string private constant NAME = "peerBalancePull";
|
|
24
|
+
uint internal immutable peerBalancePullId = peerId(NAME);
|
|
25
|
+
|
|
26
|
+
constructor() {
|
|
27
|
+
emit Peer(host, peerBalancePullId, NAME, "1:0", Schemas.Balance, "", false);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/// @notice Execute the balance-pull peer call.
|
|
31
|
+
function peerBalancePull(bytes calldata request) external onlyPeer returns (bytes memory) {
|
|
32
|
+
(Cur memory input, ) = cursor(request, 1);
|
|
33
|
+
uint peer = caller();
|
|
34
|
+
|
|
35
|
+
while (input.i < input.bound) {
|
|
36
|
+
(bytes32 asset, bytes32 meta, uint amount) = input.unpackBalance();
|
|
37
|
+
balancePull(peer, asset, meta, amount);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
input.close();
|
|
41
|
+
return "";
|
|
42
|
+
}
|
|
43
|
+
}
|
package/peer/DenyAssets.sol
CHANGED
|
@@ -15,19 +15,19 @@ abstract contract PeerDenyAssets is PeerBase, DenyAssetsHook {
|
|
|
15
15
|
uint internal immutable peerDenyAssetsId = peerId(NAME);
|
|
16
16
|
|
|
17
17
|
constructor() {
|
|
18
|
-
emit Peer(host, peerDenyAssetsId, NAME, Schemas.Asset, false);
|
|
18
|
+
emit Peer(host, peerDenyAssetsId, NAME, "1:0", Schemas.Asset, "", false);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/// @notice Execute the deny-assets peer call.
|
|
22
22
|
function peerDenyAssets(bytes calldata request) external onlyPeer returns (bytes memory) {
|
|
23
|
-
(Cur memory assets,
|
|
23
|
+
(Cur memory assets, ) = cursor(request, 1);
|
|
24
24
|
|
|
25
25
|
while (assets.i < assets.bound) {
|
|
26
26
|
(bytes32 asset, bytes32 meta) = assets.unpackAsset();
|
|
27
27
|
denyAsset(asset, meta);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
assets.
|
|
30
|
+
assets.close();
|
|
31
31
|
return "";
|
|
32
32
|
}
|
|
33
33
|
}
|
package/peer/Pipe.sol
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
|
+
pragma solidity ^0.8.33;
|
|
3
|
+
|
|
4
|
+
import {PeerBase} from "./Base.sol";
|
|
5
|
+
import {Pipeline} from "../core/Pipeline.sol";
|
|
6
|
+
import {Cursors, Cur, Schemas} from "../Cursors.sol";
|
|
7
|
+
import {Budget} from "../utils/Value.sol";
|
|
8
|
+
|
|
9
|
+
using Cursors for Cur;
|
|
10
|
+
|
|
11
|
+
/// @title PeerPipePayable
|
|
12
|
+
/// @notice Peer that consumes CONTEXT blocks and executes each context request as a STEP stream.
|
|
13
|
+
/// Each CONTEXT block carries an account, state, and request; the request is passed to the
|
|
14
|
+
/// shared pipeline as the step stream.
|
|
15
|
+
abstract contract PeerPipePayable is PeerBase, Pipeline {
|
|
16
|
+
string private constant NAME = "peerPipePayable";
|
|
17
|
+
uint internal immutable peerPipePayableId = peerId(NAME);
|
|
18
|
+
|
|
19
|
+
constructor() {
|
|
20
|
+
emit Peer(host, peerPipePayableId, NAME, "1:0", Schemas.Context, "", true);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// @notice Execute peer-supplied contexts through the shared payable pipe.
|
|
24
|
+
/// @dev Each context receives its own explicit value sub-budget. Any top-level
|
|
25
|
+
/// `msg.value` not assigned to a context remains on this host.
|
|
26
|
+
function peerPipePayable(bytes calldata request) external payable onlyPeer returns (bytes memory) {
|
|
27
|
+
(Cur memory input, ) = cursor(request, 1);
|
|
28
|
+
Budget memory budget = valueBudget();
|
|
29
|
+
|
|
30
|
+
while (input.i < input.bound) {
|
|
31
|
+
(bytes32 account, uint value, bytes calldata state, bytes calldata steps) = input.unpackContext();
|
|
32
|
+
pipe(account, state, steps, allocateValue(budget, value));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
input.close();
|
|
36
|
+
return "";
|
|
37
|
+
}
|
|
38
|
+
}
|
package/peer/Settle.sol
CHANGED
|
@@ -15,18 +15,18 @@ abstract contract PeerSettle is PeerBase, TransferHook {
|
|
|
15
15
|
uint internal immutable peerSettleId = peerId(NAME);
|
|
16
16
|
|
|
17
17
|
constructor() {
|
|
18
|
-
emit Peer(host, peerSettleId, NAME, Schemas.Transaction, false);
|
|
18
|
+
emit Peer(host, peerSettleId, NAME, "1:0", Schemas.Transaction, "", false);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/// @notice Execute the peer-settle call.
|
|
22
22
|
function peerSettle(bytes calldata request) external onlyPeer returns (bytes memory) {
|
|
23
|
-
(Cur memory state,
|
|
23
|
+
(Cur memory state, ) = cursor(request, 1);
|
|
24
24
|
|
|
25
25
|
while (state.i < state.bound) {
|
|
26
26
|
transfer(state.unpackTxValue());
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
state.
|
|
29
|
+
state.close();
|
|
30
30
|
return "";
|
|
31
31
|
}
|
|
32
32
|
}
|
package/queries/Assets.sol
CHANGED
|
@@ -26,15 +26,15 @@ abstract contract IsAllowedAsset is QueryBase, IsAllowedAssetHook {
|
|
|
26
26
|
uint public immutable isAllowedAssetId = queryId(NAME);
|
|
27
27
|
|
|
28
28
|
constructor() {
|
|
29
|
-
emit Query(host, isAllowedAssetId, NAME, Schemas.Asset, Forms.Status);
|
|
29
|
+
emit Query(host, isAllowedAssetId, NAME, "1:1", Schemas.Asset, Forms.Status);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/// @notice Resolve allowlist status for a run of requested `(asset, meta)` tuples.
|
|
33
|
-
/// @param request Block-stream request consisting of
|
|
34
|
-
/// @return Block-stream response containing one
|
|
33
|
+
/// @param request Block-stream request consisting of `#asset { bytes32 asset, bytes32 meta }` blocks.
|
|
34
|
+
/// @return Block-stream response containing one `#status { bool ok }` per asset block.
|
|
35
35
|
function isAllowedAsset(bytes calldata request) external view returns (bytes memory) {
|
|
36
|
-
(Cur memory query, uint
|
|
37
|
-
Writer memory response = Writers.allocStatuses(
|
|
36
|
+
(Cur memory query, uint groups) = cursor(request, 1);
|
|
37
|
+
Writer memory response = Writers.allocStatuses(groups);
|
|
38
38
|
|
|
39
39
|
while (query.i < query.bound) {
|
|
40
40
|
(bytes32 asset, bytes32 meta) = query.unpackAsset();
|
|
@@ -42,6 +42,7 @@ abstract contract IsAllowedAsset is QueryBase, IsAllowedAssetHook {
|
|
|
42
42
|
response.appendStatus(allowed);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
query.close();
|
|
46
|
+
return response.finish();
|
|
46
47
|
}
|
|
47
48
|
}
|