@rootzero/contracts 0.8.0 → 0.9.1
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 +17 -23
- package/Core.sol +3 -4
- package/Cursors.sol +4 -6
- package/Events.sol +3 -2
- package/Queries.sol +1 -1
- package/README.md +18 -19
- package/Utils.sol +3 -3
- package/blocks/Cursors.sol +1398 -0
- package/blocks/Keys.sol +57 -34
- package/blocks/Schema.sol +108 -126
- package/blocks/Writers.sol +476 -301
- package/commands/Base.sol +32 -22
- package/commands/Burn.sol +5 -5
- package/commands/Credit.sol +8 -8
- package/commands/Debit.sol +5 -5
- package/commands/Deposit.sol +13 -12
- package/commands/Pipe.sol +5 -5
- package/commands/Provision.sol +25 -54
- package/commands/Transfer.sol +11 -21
- package/commands/Withdraw.sol +7 -8
- package/commands/{admin → control}/AllowAssets.sol +9 -8
- package/commands/control/Allowance.sol +43 -0
- package/commands/{admin → control}/Authorize.sol +9 -8
- package/commands/{admin → control}/DenyAssets.sol +9 -8
- package/commands/{admin → control}/Destroy.sol +8 -7
- package/commands/control/Execute.sol +39 -0
- package/commands/{admin → control}/Init.sol +8 -7
- package/commands/{admin → control}/Unauthorize.sol +9 -8
- package/core/Access.sol +40 -36
- package/core/Balances.sol +17 -18
- package/core/{Operation.sol → Calls.sol} +5 -8
- package/core/{CursorBase.sol → Context.sol} +11 -5
- package/core/Host.sol +9 -9
- package/core/Types.sol +86 -0
- package/docs/GETTING_STARTED.md +37 -29
- package/events/Asset.sol +1 -1
- package/events/Command.sol +10 -10
- package/events/Control.sol +31 -0
- package/events/Deposit.sol +3 -4
- package/events/Listing.sol +1 -1
- package/events/Position.sol +21 -0
- package/events/Query.sol +5 -5
- package/events/Remote.sol +24 -0
- package/events/Withdraw.sol +2 -3
- package/package.json +1 -1
- package/queries/Assets.sol +8 -8
- package/queries/Balances.sol +11 -11
- package/queries/Base.sol +2 -3
- package/queries/Positions.sol +25 -19
- package/remote/AllowAssets.sol +39 -0
- package/remote/Allowance.sol +36 -0
- package/remote/AssetPull.sol +44 -0
- package/remote/Base.sol +40 -0
- package/remote/DenyAssets.sol +39 -0
- package/remote/Settle.sol +33 -0
- package/utils/Accounts.sol +14 -13
- package/utils/Assets.sol +77 -57
- package/utils/Ids.sol +34 -34
- package/utils/Layout.sol +4 -4
- package/utils/Utils.sol +10 -0
- package/blocks/cursors/Core.sol +0 -1121
- package/blocks/cursors/Erc1155.sol +0 -149
- package/blocks/cursors/Erc20.sol +0 -130
- package/blocks/cursors/Erc721.sol +0 -66
- package/commands/Create.sol +0 -44
- package/commands/Remove.sol +0 -44
- package/commands/Settle.sol +0 -38
- package/commands/Stake.sol +0 -49
- package/commands/Supply.sol +0 -43
- package/commands/admin/Allocate.sol +0 -43
- package/commands/admin/Relocate.sol +0 -43
- package/core/HostBound.sol +0 -14
- package/events/Erc721.sol +0 -20
- package/events/Peer.sol +0 -24
- package/peer/AllowAssets.sol +0 -39
- package/peer/AssetPull.sol +0 -44
- package/peer/Base.sol +0 -36
- package/peer/DenyAssets.sol +0 -39
- package/peer/Pull.sol +0 -41
- package/peer/Push.sol +0 -47
- package/peer/Settle.sol +0 -34
- package/utils/State.sol +0 -22
package/docs/GETTING_STARTED.md
CHANGED
|
@@ -14,7 +14,6 @@ rootzero moves data through a small command context:
|
|
|
14
14
|
|
|
15
15
|
```solidity
|
|
16
16
|
struct CommandContext {
|
|
17
|
-
uint target;
|
|
18
17
|
bytes32 account;
|
|
19
18
|
bytes state;
|
|
20
19
|
bytes request;
|
|
@@ -25,8 +24,7 @@ In practice:
|
|
|
25
24
|
|
|
26
25
|
- `account` is the user account the command is acting for.
|
|
27
26
|
- `request` is the new input for this command.
|
|
28
|
-
- `state` is
|
|
29
|
-
- `target` is the command id you expect to receive, or `0`.
|
|
27
|
+
- `state` is live pipeline state produced by an earlier command.
|
|
30
28
|
|
|
31
29
|
Most built-in commands follow a simple pattern:
|
|
32
30
|
|
|
@@ -86,7 +84,7 @@ contract ExampleHost is Host, DebitAccount {
|
|
|
86
84
|
bytes32 meta,
|
|
87
85
|
uint amount
|
|
88
86
|
) internal override {
|
|
89
|
-
bytes32 key = Assets.
|
|
87
|
+
bytes32 key = Assets.slot(asset, meta);
|
|
90
88
|
balances[account][key] -= amount;
|
|
91
89
|
}
|
|
92
90
|
}
|
|
@@ -105,22 +103,21 @@ The built-in commands are easiest to use when you know which blocks they expect.
|
|
|
105
103
|
### Commands That Read `request`
|
|
106
104
|
|
|
107
105
|
- `deposit`: reads `AMOUNT` blocks, returns `BALANCE`
|
|
108
|
-
- `transfer`: reads `
|
|
106
|
+
- `transfer`: reads `PAYOUT` blocks
|
|
109
107
|
- `debitAccount`: reads `AMOUNT`, returns `BALANCE`
|
|
110
|
-
- `provision`: reads `
|
|
111
|
-
- `
|
|
108
|
+
- `provision`: reads `ALLOCATION`, returns `CUSTODY` state
|
|
109
|
+
- `pipePayable`: reads `STEP` blocks and runs them in order
|
|
112
110
|
|
|
113
111
|
### Commands That Read `state`
|
|
114
112
|
|
|
115
|
-
- `withdraw`: reads `BALANCE`, optionally reads `
|
|
116
|
-
- `creditAccount`: reads `BALANCE`, optionally reads `
|
|
117
|
-
- `settle`: reads `TX`
|
|
118
|
-
- `provisionFromBalance`: reads `BALANCE` from `state` and `NODE` from `request`
|
|
113
|
+
- `withdraw`: reads `BALANCE`, optionally reads `ACCOUNT` from `request`
|
|
114
|
+
- `creditAccount`: reads `BALANCE`, optionally reads `ACCOUNT` from `request`
|
|
119
115
|
|
|
120
116
|
This is the main pattern to keep in mind:
|
|
121
117
|
|
|
122
118
|
- use `request` for the command's direct input
|
|
123
|
-
- use `state`
|
|
119
|
+
- use `state` for live value threaded through a pipeline, currently `BALANCE` and `CUSTODY`
|
|
120
|
+
- use peer commands such as `peerSettle` for settlement messages such as `TRANSACTION`
|
|
124
121
|
|
|
125
122
|
## Step 4: Send A Simple Request
|
|
126
123
|
|
|
@@ -137,7 +134,6 @@ const meta = ethers.ZeroHash;
|
|
|
137
134
|
const amount = 100n;
|
|
138
135
|
|
|
139
136
|
const ctx = {
|
|
140
|
-
target: 0n,
|
|
141
137
|
account: "0x...", // 32-byte rootzero account id
|
|
142
138
|
state: "0x",
|
|
143
139
|
request: encodeAmountBlock(asset, meta, amount),
|
|
@@ -160,11 +156,10 @@ When the built-in modules are not enough, add your own command entrypoint.
|
|
|
160
156
|
// SPDX-License-Identifier: MIT
|
|
161
157
|
pragma solidity ^0.8.33;
|
|
162
158
|
|
|
163
|
-
import {
|
|
164
|
-
import {
|
|
165
|
-
import {Cursors, Cursor, Schemas} from "@rootzero/contracts/Cursors.sol";
|
|
159
|
+
import {CommandBase, CommandContext, Keys} from "@rootzero/contracts/Commands.sol";
|
|
160
|
+
import {Cursors, Cur, Schemas} from "@rootzero/contracts/Cursors.sol";
|
|
166
161
|
|
|
167
|
-
using Cursors for
|
|
162
|
+
using Cursors for Cur;
|
|
168
163
|
|
|
169
164
|
string constant NAME = "myCommand";
|
|
170
165
|
string constant ROUTE = "route(uint foo, uint bar)";
|
|
@@ -174,16 +169,20 @@ abstract contract MyCommand is CommandBase {
|
|
|
174
169
|
uint internal immutable myCommandId = commandId(NAME);
|
|
175
170
|
|
|
176
171
|
constructor() {
|
|
177
|
-
emit Command(host, NAME, INPUT,
|
|
172
|
+
emit Command(host, myCommandId, NAME, INPUT, Keys.Empty, Keys.Balance, false);
|
|
178
173
|
}
|
|
179
174
|
|
|
180
175
|
function myCommand(
|
|
181
176
|
CommandContext calldata c
|
|
182
|
-
) external
|
|
183
|
-
|
|
184
|
-
uint
|
|
177
|
+
) external onlyCommand(c.account) returns (bytes memory) {
|
|
178
|
+
Cur memory input = cursor(c.request);
|
|
179
|
+
uint next = input.bundle();
|
|
180
|
+
|
|
181
|
+
bytes calldata route = input.unpackRaw(Keys.Route);
|
|
185
182
|
(bytes32 asset, bytes32 meta, uint amount) = input.unpackAmount();
|
|
186
|
-
|
|
183
|
+
input.ensure(next);
|
|
184
|
+
|
|
185
|
+
route;
|
|
187
186
|
return Cursors.toBalanceBlock(asset, meta, amount);
|
|
188
187
|
}
|
|
189
188
|
}
|
|
@@ -193,7 +192,7 @@ There are three important ideas here:
|
|
|
193
192
|
|
|
194
193
|
- every custom command gets a deterministic command id
|
|
195
194
|
- you announce it with the `Command` event
|
|
196
|
-
- `onlyCommand(
|
|
195
|
+
- `onlyCommand(c.account)` ensures the caller is trusted and the calldata account matches `c.account`
|
|
197
196
|
|
|
198
197
|
## Step 6: Read Input With A Cursor
|
|
199
198
|
|
|
@@ -205,7 +204,7 @@ If your request contains a bundled input like:
|
|
|
205
204
|
|
|
206
205
|
your command can:
|
|
207
206
|
|
|
208
|
-
- open it with `Cursors.
|
|
207
|
+
- open it with `cursor(c.request)` or `Cursors.open(...)`
|
|
209
208
|
- consume the route first
|
|
210
209
|
- then consume the amount
|
|
211
210
|
- keep parsing in bundle/member order without indexing helpers
|
|
@@ -232,18 +231,27 @@ function buildBalances() internal pure returns (bytes memory) {
|
|
|
232
231
|
Writer memory writer = Writers.allocBalances(2);
|
|
233
232
|
writer.appendBalance(bytes32(uint256(1)), bytes32(0), 50);
|
|
234
233
|
writer.appendBalance(bytes32(uint256(2)), bytes32(0), 75);
|
|
235
|
-
return writer.
|
|
234
|
+
return writer.finish();
|
|
236
235
|
}
|
|
237
236
|
```
|
|
238
237
|
|
|
239
238
|
Use this when your command needs to return:
|
|
240
239
|
|
|
241
240
|
- balances
|
|
242
|
-
-
|
|
243
|
-
- transactions
|
|
241
|
+
- custody state
|
|
244
242
|
|
|
245
243
|
If you are only consuming built-in commands, you often will not need to touch writers directly.
|
|
246
244
|
|
|
245
|
+
## Query Forms
|
|
246
|
+
|
|
247
|
+
Queries use reusable `Forms` as their input and output schema vocabulary. For example, `getBalances` accepts `Forms.AccountAsset` blocks and returns `Forms.AccountAmount` blocks:
|
|
248
|
+
|
|
249
|
+
```solidity
|
|
250
|
+
emit Query(host, NAME, Forms.AccountAsset, Forms.AccountAmount, getBalancesId);
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
This keeps query payloads structural: the query name describes what is being asked, while the form describes the fields carried by each block.
|
|
254
|
+
|
|
247
255
|
## A Tiny End-To-End Example
|
|
248
256
|
|
|
249
257
|
Imagine you want a host that keeps internal balances and lets rootzero debit them.
|
|
@@ -251,7 +259,7 @@ Imagine you want a host that keeps internal balances and lets rootzero debit the
|
|
|
251
259
|
1. Deploy a host that inherits `Host` and `DebitAccount`.
|
|
252
260
|
2. Store balances in your own mapping.
|
|
253
261
|
3. Implement `debitAccount(account, asset, meta, amount)`.
|
|
254
|
-
4. Send `
|
|
262
|
+
4. Send `debitAccount` a request containing one or more `AMOUNT` blocks.
|
|
255
263
|
5. rootzero returns `BALANCE` blocks representing the debited amounts.
|
|
256
264
|
|
|
257
265
|
That is already a valid and useful integration.
|
|
@@ -272,7 +280,7 @@ If you want to learn by example, these are the best files to read next:
|
|
|
272
280
|
|
|
273
281
|
- Passing data in `state` when the command expects it in `request`
|
|
274
282
|
- Forgetting to emit a `Command` event for a custom command
|
|
275
|
-
- Using
|
|
283
|
+
- Using an admin account with user-only command flows such as `pipePayable`
|
|
276
284
|
- Trying to parse raw bytes manually when a built-in reader already exists
|
|
277
285
|
- Starting with a custom command when a built-in module already matches the job
|
|
278
286
|
|
package/events/Asset.sol
CHANGED
|
@@ -8,7 +8,7 @@ string constant ABI = "event Asset(uint indexed host, bytes32 name, uint32 prefi
|
|
|
8
8
|
/// @notice Emitted when an asset is registered or updated on a host.
|
|
9
9
|
abstract contract AssetEvent is EventEmitter {
|
|
10
10
|
/// @param host Host node ID that registered the asset.
|
|
11
|
-
/// @param name Asset
|
|
11
|
+
/// @param name Asset name encoded as a bytes32 string.
|
|
12
12
|
/// @param prefix 4-byte type prefix of the asset ID.
|
|
13
13
|
event Asset(uint indexed host, bytes32 name, uint32 prefix);
|
|
14
14
|
|
package/events/Command.sol
CHANGED
|
@@ -4,24 +4,24 @@ 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,
|
|
7
|
+
"event Command(uint indexed host, uint id, string name, string request, bytes4 state, bytes4 output, bool acceptsValue)";
|
|
8
8
|
|
|
9
|
-
/// @notice Emitted once per command during host deployment to publish its schema and state
|
|
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
|
+
/// @param id Command node ID.
|
|
12
13
|
/// @param name Human-readable command name.
|
|
13
|
-
/// @param
|
|
14
|
-
/// @param
|
|
15
|
-
/// @param
|
|
16
|
-
/// @param stateOut State type discriminant for the output state.
|
|
14
|
+
/// @param request Schema DSL string describing the request shape.
|
|
15
|
+
/// @param state Block key expected for input state, or `Keys.Empty`.
|
|
16
|
+
/// @param output Block key produced for output state, or `Keys.Empty`.
|
|
17
17
|
/// @param acceptsValue Whether the command entrypoint accepts nonzero `msg.value`.
|
|
18
18
|
event Command(
|
|
19
19
|
uint indexed host,
|
|
20
|
+
uint id,
|
|
20
21
|
string name,
|
|
21
|
-
string
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
uint8 stateOut,
|
|
22
|
+
string request,
|
|
23
|
+
bytes4 state,
|
|
24
|
+
bytes4 output,
|
|
25
25
|
bool acceptsValue
|
|
26
26
|
);
|
|
27
27
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
|
+
pragma solidity ^0.8.33;
|
|
3
|
+
|
|
4
|
+
import { EventEmitter } from "./Emitter.sol";
|
|
5
|
+
|
|
6
|
+
string constant ABI =
|
|
7
|
+
"event Control(uint indexed host, uint id, string name, string request, bytes4 state, bytes4 output, bool acceptsValue)";
|
|
8
|
+
|
|
9
|
+
/// @notice Emitted once per control command during host deployment to publish its request schema and state keys.
|
|
10
|
+
abstract contract ControlEvent is EventEmitter {
|
|
11
|
+
/// @param host Host node ID that owns this control command.
|
|
12
|
+
/// @param id Command node ID.
|
|
13
|
+
/// @param name Human-readable command name.
|
|
14
|
+
/// @param request Schema DSL string describing the request shape.
|
|
15
|
+
/// @param state Block key expected for input state, or `Keys.Empty`.
|
|
16
|
+
/// @param output Block key produced for output state, or `Keys.Empty`.
|
|
17
|
+
/// @param acceptsValue Whether the command entrypoint accepts nonzero `msg.value`.
|
|
18
|
+
event Control(
|
|
19
|
+
uint indexed host,
|
|
20
|
+
uint id,
|
|
21
|
+
string name,
|
|
22
|
+
string request,
|
|
23
|
+
bytes4 state,
|
|
24
|
+
bytes4 output,
|
|
25
|
+
bool acceptsValue
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
constructor() {
|
|
29
|
+
emit EventAbi(ABI);
|
|
30
|
+
}
|
|
31
|
+
}
|
package/events/Deposit.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 Deposit(bytes32 indexed account, bytes32 asset, bytes32 meta, uint amount
|
|
6
|
+
string constant ABI = "event Deposit(bytes32 indexed account, bytes32 asset, bytes32 meta, uint amount)";
|
|
7
7
|
|
|
8
8
|
/// @notice Emitted when assets are deposited into an account.
|
|
9
9
|
abstract contract DepositEvent is EventEmitter {
|
|
10
|
-
/// @param account
|
|
10
|
+
/// @param account Destination account identifier.
|
|
11
11
|
/// @param asset Asset identifier.
|
|
12
12
|
/// @param meta Asset metadata slot.
|
|
13
13
|
/// @param amount Amount deposited.
|
|
14
|
-
|
|
15
|
-
event Deposit(bytes32 indexed account, bytes32 asset, bytes32 meta, uint amount, uint cid);
|
|
14
|
+
event Deposit(bytes32 indexed account, bytes32 asset, bytes32 meta, uint amount);
|
|
16
15
|
|
|
17
16
|
constructor() {
|
|
18
17
|
emit EventAbi(ABI);
|
package/events/Listing.sol
CHANGED
|
@@ -11,7 +11,7 @@ abstract contract ListingEvent is EventEmitter {
|
|
|
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
|
-
/// @param created True if
|
|
14
|
+
/// @param created True if the asset was created as part of this listing.
|
|
15
15
|
event Listing(uint indexed host, bytes32 asset, bytes32 meta, bool active, bool created);
|
|
16
16
|
|
|
17
17
|
constructor() {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
|
+
pragma solidity ^0.8.33;
|
|
3
|
+
|
|
4
|
+
import {EventEmitter} from "./Emitter.sol";
|
|
5
|
+
|
|
6
|
+
string constant ABI = "event AssetPosition(bytes32 indexed account, bytes32 asset, bytes32 meta, uint value, uint queryId)";
|
|
7
|
+
|
|
8
|
+
/// @notice Emitted when the reported value of an asset-backed position changes or is observed.
|
|
9
|
+
/// A value of 0 should be interpreted as a closed position.
|
|
10
|
+
abstract contract AssetPositionEvent is EventEmitter {
|
|
11
|
+
/// @param account Account identifier that owns or is associated with the position.
|
|
12
|
+
/// @param asset Asset identifier for the asset class.
|
|
13
|
+
/// @param meta Asset metadata slot carrying the position context.
|
|
14
|
+
/// @param value Context-specific position value; 0 indicates a closed position.
|
|
15
|
+
/// @param queryId Query ID associated with the position lookup or reporting context.
|
|
16
|
+
event AssetPosition(bytes32 indexed account, bytes32 asset, bytes32 meta, uint value, uint queryId);
|
|
17
|
+
|
|
18
|
+
constructor() {
|
|
19
|
+
emit EventAbi(ABI);
|
|
20
|
+
}
|
|
21
|
+
}
|
package/events/Query.sol
CHANGED
|
@@ -3,16 +3,16 @@ 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,
|
|
6
|
+
string constant ABI = "event Query(uint indexed host, uint id, string name, 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
|
+
/// @param id Query node ID.
|
|
11
12
|
/// @param name Human-readable query name.
|
|
12
|
-
/// @param
|
|
13
|
-
/// @param
|
|
14
|
-
|
|
15
|
-
event Query(uint indexed host, string name, string input, string output, uint qid);
|
|
13
|
+
/// @param request Schema DSL string describing the query request shape.
|
|
14
|
+
/// @param response Schema DSL string describing the query response shape.
|
|
15
|
+
event Query(uint indexed host, uint id, string name, string request, string response);
|
|
16
16
|
|
|
17
17
|
constructor() {
|
|
18
18
|
emit EventAbi(ABI);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
|
+
pragma solidity ^0.8.33;
|
|
3
|
+
|
|
4
|
+
import { EventEmitter } from "./Emitter.sol";
|
|
5
|
+
|
|
6
|
+
string constant ABI =
|
|
7
|
+
"event Remote(uint indexed host, uint id, string name, string request, bool acceptsValue)";
|
|
8
|
+
|
|
9
|
+
/// @notice Emitted once per remote during host deployment to publish its request schema.
|
|
10
|
+
abstract contract RemoteEvent is EventEmitter {
|
|
11
|
+
/// @param host Host node ID that owns this remote.
|
|
12
|
+
/// @param id Remote node ID.
|
|
13
|
+
/// @param name Human-readable remote name.
|
|
14
|
+
/// @param request Schema DSL string describing the remote request shape.
|
|
15
|
+
/// @param acceptsValue Whether the remote entrypoint accepts nonzero `msg.value`.
|
|
16
|
+
event Remote(uint indexed host, uint id, string name, string request, bool acceptsValue);
|
|
17
|
+
|
|
18
|
+
constructor() {
|
|
19
|
+
emit EventAbi(ABI);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
package/events/Withdraw.sol
CHANGED
|
@@ -3,7 +3,7 @@ pragma solidity ^0.8.33;
|
|
|
3
3
|
|
|
4
4
|
import { EventEmitter } from "./Emitter.sol";
|
|
5
5
|
|
|
6
|
-
string constant ABI = "event Withdrawal(bytes32 indexed account, bytes32 asset, bytes32 meta, uint amount
|
|
6
|
+
string constant ABI = "event Withdrawal(bytes32 indexed account, bytes32 asset, bytes32 meta, uint amount)";
|
|
7
7
|
|
|
8
8
|
/// @notice Emitted when assets are withdrawn from an account.
|
|
9
9
|
abstract contract WithdrawalEvent is EventEmitter {
|
|
@@ -11,8 +11,7 @@ abstract contract WithdrawalEvent is EventEmitter {
|
|
|
11
11
|
/// @param asset Asset identifier.
|
|
12
12
|
/// @param meta Asset metadata slot.
|
|
13
13
|
/// @param amount Amount withdrawn.
|
|
14
|
-
|
|
15
|
-
event Withdrawal(bytes32 indexed account, bytes32 asset, bytes32 meta, uint amount, uint cid);
|
|
14
|
+
event Withdrawal(bytes32 indexed account, bytes32 asset, bytes32 meta, uint amount);
|
|
16
15
|
|
|
17
16
|
constructor() {
|
|
18
17
|
emit EventAbi(ABI);
|
package/package.json
CHANGED
package/queries/Assets.sol
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
2
|
pragma solidity ^0.8.33;
|
|
3
3
|
|
|
4
|
-
import {Cur, Cursors, Writer, Writers
|
|
5
|
-
import {Schemas} from "../blocks/Schema.sol";
|
|
4
|
+
import {Cur, Cursors, Writer, Writers} from "../Cursors.sol";
|
|
5
|
+
import {Forms, Schemas} from "../blocks/Schema.sol";
|
|
6
6
|
import {QueryBase} from "./Base.sol";
|
|
7
7
|
|
|
8
8
|
using Cursors for Cur;
|
|
9
|
+
using Writers for Writer;
|
|
9
10
|
|
|
10
11
|
string constant NAME = "isAllowedAsset";
|
|
11
|
-
string constant OUTPUT = "response(uint allowed)";
|
|
12
12
|
|
|
13
13
|
abstract contract IsAllowedAssetHook {
|
|
14
14
|
/// @notice Resolve whether one asset tuple is allowed.
|
|
@@ -22,25 +22,25 @@ abstract contract IsAllowedAssetHook {
|
|
|
22
22
|
/// @title IsAllowedAsset
|
|
23
23
|
/// @notice Rootzero query that checks whether one or more `(asset, meta)` tuples are allowed.
|
|
24
24
|
/// The request is a run of `ASSET` blocks.
|
|
25
|
-
/// The response returns one `
|
|
25
|
+
/// The response returns one `STATUS` form block per query entry, preserving request order.
|
|
26
26
|
abstract contract IsAllowedAsset is QueryBase, IsAllowedAssetHook {
|
|
27
27
|
uint public immutable isAllowedAssetId = queryId(NAME);
|
|
28
28
|
|
|
29
29
|
constructor() {
|
|
30
|
-
emit Query(host, NAME, Schemas.Asset,
|
|
30
|
+
emit Query(host, isAllowedAssetId, NAME, Schemas.Asset, Forms.Status);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/// @notice Resolve allowlist status for a run of requested `(asset, meta)` tuples.
|
|
34
34
|
/// @param request Block-stream request consisting of `asset(asset, meta)*`.
|
|
35
|
-
/// @return Block-stream response containing one `
|
|
35
|
+
/// @return Block-stream response containing one `status(ok)` per asset block.
|
|
36
36
|
function isAllowedAsset(bytes calldata request) external view returns (bytes memory) {
|
|
37
37
|
(Cur memory query, uint count, ) = cursor(request, 1);
|
|
38
|
-
Writer memory response = Writers.
|
|
38
|
+
Writer memory response = Writers.allocStatuses(count);
|
|
39
39
|
|
|
40
40
|
while (query.i < query.bound) {
|
|
41
41
|
(bytes32 asset, bytes32 meta) = query.unpackAsset();
|
|
42
42
|
bool allowed = isAllowedAsset(asset, meta);
|
|
43
|
-
|
|
43
|
+
response.appendStatus(allowed);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
return query.complete(response);
|
package/queries/Balances.sol
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
2
|
pragma solidity ^0.8.33;
|
|
3
3
|
|
|
4
|
-
import {Cur, Cursors,
|
|
4
|
+
import {Cur, Cursors, Forms, Writer, Writers} from "../Cursors.sol";
|
|
5
5
|
import {QueryBase} from "./Base.sol";
|
|
6
6
|
|
|
7
7
|
using Cursors for Cur;
|
|
8
|
+
using Writers for Writer;
|
|
8
9
|
|
|
9
10
|
string constant NAME = "getBalances";
|
|
10
|
-
string constant INPUT = "query(bytes32 account, bytes32 asset, bytes32 meta)";
|
|
11
11
|
|
|
12
12
|
abstract contract GetBalancesHook {
|
|
13
13
|
/// @notice Resolve one account's balance for one supported asset.
|
|
@@ -21,26 +21,26 @@ abstract contract GetBalancesHook {
|
|
|
21
21
|
|
|
22
22
|
/// @title GetBalances
|
|
23
23
|
/// @notice Rootzero query that resolves balances for one or more `(account, asset, meta)` tuples.
|
|
24
|
-
/// The request is a run of `
|
|
25
|
-
/// The response returns one `
|
|
24
|
+
/// The request is a run of `ACCOUNT_ASSET` form blocks.
|
|
25
|
+
/// The response returns one `ACCOUNT_AMOUNT` form block per requested position, preserving request order.
|
|
26
26
|
abstract contract GetBalances is QueryBase, GetBalancesHook {
|
|
27
27
|
uint public immutable getBalancesId = queryId(NAME);
|
|
28
28
|
|
|
29
29
|
constructor() {
|
|
30
|
-
emit Query(host,
|
|
30
|
+
emit Query(host, getBalancesId, NAME, Forms.AccountAsset, Forms.AccountAmount);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/// @notice Resolve balances for a run of requested `(account, asset, meta)` tuples.
|
|
34
|
-
/// @param request Block-stream request consisting of `
|
|
35
|
-
/// @return Block-stream response containing one `
|
|
34
|
+
/// @param request Block-stream request consisting of `accountAsset(account, asset, meta)*`.
|
|
35
|
+
/// @return Block-stream response containing one `accountAmount(account, asset, meta, amount)` block per request block.
|
|
36
36
|
function getBalances(bytes calldata request) external view returns (bytes memory) {
|
|
37
37
|
(Cur memory query, uint count, ) = cursor(request, 1);
|
|
38
|
-
Writer memory response = Writers.
|
|
38
|
+
Writer memory response = Writers.allocAccountAmounts(count);
|
|
39
39
|
|
|
40
40
|
while (query.i < query.bound) {
|
|
41
|
-
(bytes32 account, bytes32 asset, bytes32 meta) = query.
|
|
42
|
-
uint
|
|
43
|
-
|
|
41
|
+
(bytes32 account, bytes32 asset, bytes32 meta) = query.unpackAccountAsset();
|
|
42
|
+
uint amount = getBalance(account, asset, meta);
|
|
43
|
+
response.appendAccountAmount(account, asset, meta, amount);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
return query.complete(response);
|
package/queries/Base.sol
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
2
|
pragma solidity ^0.8.33;
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import { HostBound } from "../core/HostBound.sol";
|
|
4
|
+
import { RootZeroContext } from "../core/Context.sol";
|
|
6
5
|
import { QueryEvent } from "../events/Query.sol";
|
|
7
6
|
import { Ids, Selectors } from "../utils/Ids.sol";
|
|
8
7
|
|
|
@@ -21,7 +20,7 @@ function encodeQueryCall(uint target, bytes calldata request) pure returns (byte
|
|
|
21
20
|
/// @notice Abstract base for rootzero query contracts.
|
|
22
21
|
/// Queries are view-only entry points that consume a block-stream request and
|
|
23
22
|
/// return a block-stream response.
|
|
24
|
-
abstract contract QueryBase is
|
|
23
|
+
abstract contract QueryBase is RootZeroContext, QueryEvent {
|
|
25
24
|
|
|
26
25
|
/// @notice Derive the deterministic node ID for a named query on this contract.
|
|
27
26
|
/// The ID encodes the ABI selector of `name(bytes)` and `address(this)`,
|
package/queries/Positions.sol
CHANGED
|
@@ -1,49 +1,55 @@
|
|
|
1
1
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
2
|
pragma solidity ^0.8.33;
|
|
3
3
|
|
|
4
|
-
import {Cur, Cursors, Writer, Writers
|
|
5
|
-
import {
|
|
4
|
+
import {Cur, Cursors, Writer, Writers} from "../Cursors.sol";
|
|
5
|
+
import {Forms} from "../blocks/Schema.sol";
|
|
6
6
|
import {QueryBase} from "./Base.sol";
|
|
7
7
|
|
|
8
8
|
using Cursors for Cur;
|
|
9
9
|
|
|
10
|
-
string constant NAME = "
|
|
10
|
+
string constant NAME = "getPosition";
|
|
11
11
|
|
|
12
|
-
abstract contract
|
|
13
|
-
/// @notice Resolve the position payload for one requested
|
|
12
|
+
abstract contract GetPositionHook {
|
|
13
|
+
/// @notice Resolve the position payload for one requested position.
|
|
14
14
|
/// Concrete implementations must append exactly one `RESPONSE` block whose payload
|
|
15
15
|
/// length matches `positionResponseSize`.
|
|
16
|
+
/// @param account Requested account identifier.
|
|
16
17
|
/// @param asset Requested asset identifier.
|
|
17
18
|
/// @param meta Requested asset metadata slot.
|
|
18
19
|
/// @param response Destination writer for the response stream.
|
|
19
|
-
function
|
|
20
|
+
function appendPosition(
|
|
21
|
+
bytes32 account,
|
|
22
|
+
bytes32 asset,
|
|
23
|
+
bytes32 meta,
|
|
24
|
+
Writer memory response
|
|
25
|
+
) internal view virtual;
|
|
20
26
|
}
|
|
21
27
|
|
|
22
|
-
/// @title
|
|
23
|
-
/// @notice Rootzero query that resolves one dynamic position response for each requested
|
|
24
|
-
/// The request is a run of `
|
|
25
|
-
/// The response returns one dynamic `RESPONSE` block per
|
|
26
|
-
abstract contract
|
|
27
|
-
uint public immutable
|
|
28
|
+
/// @title GetPosition
|
|
29
|
+
/// @notice Rootzero query that resolves one dynamic position response for each requested position.
|
|
30
|
+
/// The request is a run of `ACCOUNT_ASSET` form blocks.
|
|
31
|
+
/// The response returns one dynamic `RESPONSE` block per position entry, preserving request order.
|
|
32
|
+
abstract contract GetPosition is QueryBase, GetPositionHook {
|
|
33
|
+
uint public immutable getPositionId = queryId(NAME);
|
|
28
34
|
uint internal immutable positionResponseSize;
|
|
29
35
|
|
|
30
36
|
constructor(string memory output, uint responseSize) {
|
|
31
37
|
positionResponseSize = responseSize;
|
|
32
|
-
emit Query(host, NAME,
|
|
38
|
+
emit Query(host, getPositionId, NAME, Forms.AccountAsset, output);
|
|
33
39
|
}
|
|
34
40
|
|
|
35
|
-
/// @notice Resolve positions for a run of requested `(asset, meta)` tuples.
|
|
41
|
+
/// @notice Resolve positions for a run of requested `(account, asset, meta)` tuples.
|
|
36
42
|
/// @dev Allocates from the configured fixed response payload length so each hook call
|
|
37
43
|
/// can append one `RESPONSE` block directly into the output stream.
|
|
38
|
-
/// @param request Block-stream request consisting of `
|
|
39
|
-
/// @return Block-stream response containing one `response(bytes data)` block per
|
|
40
|
-
function
|
|
44
|
+
/// @param request Block-stream request consisting of `accountAsset(account, asset, meta)*`.
|
|
45
|
+
/// @return Block-stream response containing one `response(bytes data)` block per position block.
|
|
46
|
+
function getPosition(bytes calldata request) external view returns (bytes memory) {
|
|
41
47
|
(Cur memory query, uint count, ) = cursor(request, 1);
|
|
42
48
|
Writer memory response = Writers.allocBytes(count, positionResponseSize);
|
|
43
49
|
|
|
44
50
|
while (query.i < query.bound) {
|
|
45
|
-
(bytes32 asset, bytes32 meta) = query.
|
|
46
|
-
|
|
51
|
+
(bytes32 account, bytes32 asset, bytes32 meta) = query.unpackAccountAsset();
|
|
52
|
+
appendPosition(account, asset, meta, response);
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
return query.complete(response);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
|
+
pragma solidity ^0.8.33;
|
|
3
|
+
|
|
4
|
+
import { RemoteBase } from "./Base.sol";
|
|
5
|
+
import { AllowAssetsHook } from "../commands/control/AllowAssets.sol";
|
|
6
|
+
import { Cursors, Cur, Schemas } from "../Cursors.sol";
|
|
7
|
+
|
|
8
|
+
using Cursors for Cur;
|
|
9
|
+
|
|
10
|
+
string constant NAME = "remoteAllowAssets";
|
|
11
|
+
|
|
12
|
+
/// @title RemoteAllowAssets
|
|
13
|
+
/// @notice Remote that permits a list of (asset, meta) pairs on behalf of a remote host.
|
|
14
|
+
/// Each ASSET block in the request calls `allowAsset`. Restricted to trusted remotes.
|
|
15
|
+
abstract contract RemoteAllowAssets is RemoteBase, AllowAssetsHook {
|
|
16
|
+
uint internal immutable remoteAllowAssetsId = remoteId(NAME);
|
|
17
|
+
|
|
18
|
+
constructor() {
|
|
19
|
+
emit Remote(host, remoteAllowAssetsId, NAME, Schemas.Asset, false);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/// @notice Execute the allow-assets remote call.
|
|
23
|
+
function remoteAllowAssets(bytes calldata request) external onlyRemote returns (bytes memory) {
|
|
24
|
+
(Cur memory assets, , ) = cursor(request, 1);
|
|
25
|
+
|
|
26
|
+
while (assets.i < assets.bound) {
|
|
27
|
+
(bytes32 asset, bytes32 meta) = assets.unpackAsset();
|
|
28
|
+
allowAsset(asset, meta);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
assets.complete();
|
|
32
|
+
return "";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|