@rootzero/contracts 0.8.0 → 0.9.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/Commands.sol +5 -10
- package/Core.sol +3 -4
- package/Cursors.sol +4 -6
- package/Events.sol +1 -1
- package/Queries.sol +1 -1
- package/README.md +18 -19
- package/Utils.sol +3 -3
- package/blocks/Cursors.sol +1437 -0
- package/blocks/Keys.sol +59 -34
- package/blocks/Schema.sol +109 -126
- package/blocks/Writers.sol +476 -301
- package/commands/Base.sol +32 -22
- package/commands/Burn.sol +3 -3
- package/commands/Credit.sol +6 -7
- package/commands/Debit.sol +3 -3
- package/commands/Deposit.sol +7 -7
- package/commands/Pipe.sol +3 -3
- package/commands/Provision.sol +19 -49
- package/commands/Transfer.sol +9 -19
- package/commands/Withdraw.sol +5 -6
- package/commands/admin/AllowAssets.sol +3 -3
- package/commands/admin/Allowance.sol +43 -0
- package/commands/admin/Authorize.sol +4 -4
- package/commands/admin/DenyAssets.sol +3 -3
- package/commands/admin/Destroy.sol +3 -3
- package/commands/admin/Execute.sol +38 -0
- package/commands/admin/Init.sol +3 -3
- package/commands/admin/Relocate.sol +5 -5
- package/commands/admin/Unauthorize.sol +4 -4
- package/core/Access.sol +38 -34
- 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 +10 -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/Deposit.sol +3 -4
- package/events/Listing.sol +1 -1
- package/events/Peer.sol +3 -3
- package/events/Position.sol +21 -0
- package/events/Query.sol +3 -3
- package/events/Withdraw.sol +2 -3
- package/package.json +1 -1
- package/peer/AllowAssets.sol +1 -1
- package/peer/Allowance.sol +36 -0
- package/peer/AssetPull.sol +1 -1
- package/peer/Base.sol +8 -4
- package/peer/DenyAssets.sol +1 -1
- package/peer/Settle.sol +3 -4
- 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/utils/Accounts.sol +14 -13
- package/utils/Assets.sol +77 -57
- package/utils/Ids.sol +4 -4
- package/utils/Layout.sol +1 -1
- 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/core/HostBound.sol +0 -14
- package/events/Erc721.sol +0 -20
- package/peer/Pull.sol +0 -41
- package/peer/Push.sol +0 -47
- 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
|
|
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() {
|
package/events/Peer.sol
CHANGED
|
@@ -4,16 +4,16 @@ 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, string name, string schema,
|
|
7
|
+
"event Peer(uint indexed host, uint id, string name, string schema, bool acceptsValue)";
|
|
8
8
|
|
|
9
9
|
/// @notice Emitted once per peer during host deployment to publish its schema.
|
|
10
10
|
abstract contract PeerEvent is EventEmitter {
|
|
11
11
|
/// @param host Host node ID that owns this peer.
|
|
12
|
+
/// @param id Peer node ID.
|
|
12
13
|
/// @param name Human-readable peer name.
|
|
13
14
|
/// @param schema Schema DSL string describing the peer request shape.
|
|
14
|
-
/// @param pid Peer node ID.
|
|
15
15
|
/// @param acceptsValue Whether the peer entrypoint accepts nonzero `msg.value`.
|
|
16
|
-
event Peer(uint indexed host, string name, string schema,
|
|
16
|
+
event Peer(uint indexed host, uint id, string name, string schema, bool acceptsValue);
|
|
17
17
|
|
|
18
18
|
constructor() {
|
|
19
19
|
emit EventAbi(ABI);
|
|
@@ -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, string name, string input, string output
|
|
6
|
+
string constant ABI = "event Query(uint indexed host, uint id, string name, string input, string output)";
|
|
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
13
|
/// @param input Schema DSL string describing the query request shape.
|
|
13
14
|
/// @param output Schema DSL string describing the query response shape.
|
|
14
|
-
|
|
15
|
-
event Query(uint indexed host, string name, string input, string output, uint qid);
|
|
15
|
+
event Query(uint indexed host, uint id, string name, string input, string output);
|
|
16
16
|
|
|
17
17
|
constructor() {
|
|
18
18
|
emit EventAbi(ABI);
|
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/peer/AllowAssets.sol
CHANGED
|
@@ -16,7 +16,7 @@ abstract contract PeerAllowAssets is PeerBase, AllowAssetsHook {
|
|
|
16
16
|
uint internal immutable peerAllowAssetsId = peerId(NAME);
|
|
17
17
|
|
|
18
18
|
constructor() {
|
|
19
|
-
emit Peer(host, NAME, Schemas.Asset,
|
|
19
|
+
emit Peer(host, peerAllowAssetsId, NAME, Schemas.Asset, false);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
/// @notice Execute the allow-assets peer call.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
|
+
pragma solidity ^0.8.33;
|
|
3
|
+
|
|
4
|
+
import {PeerBase} from "./Base.sol";
|
|
5
|
+
import {AllowanceHook} from "../commands/admin/Allowance.sol";
|
|
6
|
+
import {Cursors, Cur, Schemas} from "../Cursors.sol";
|
|
7
|
+
|
|
8
|
+
using Cursors for Cur;
|
|
9
|
+
|
|
10
|
+
string constant NAME = "peerAllowance";
|
|
11
|
+
|
|
12
|
+
/// @title PeerAllowance
|
|
13
|
+
/// @notice Peer that lets a trusted remote host request or refresh its own allowance.
|
|
14
|
+
/// Each AMOUNT block in the request is scoped to `caller()` and passed to the shared
|
|
15
|
+
/// allowance hook as a host-scoped allowance. Restricted to trusted peers.
|
|
16
|
+
abstract contract PeerAllowance is PeerBase, AllowanceHook {
|
|
17
|
+
uint internal immutable peerAllowanceId = peerId(NAME);
|
|
18
|
+
|
|
19
|
+
constructor() {
|
|
20
|
+
emit Peer(host, peerAllowanceId, NAME, Schemas.Amount, false);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// @notice Execute the allowance peer call.
|
|
24
|
+
function peerAllowance(bytes calldata request) external onlyPeer returns (bytes memory) {
|
|
25
|
+
(Cur memory amounts, , ) = cursor(request, 1);
|
|
26
|
+
uint peer = caller();
|
|
27
|
+
|
|
28
|
+
while (amounts.i < amounts.bound) {
|
|
29
|
+
(bytes32 asset, bytes32 meta, uint amount) = amounts.unpackAmount();
|
|
30
|
+
allowance(peer, asset, meta, amount);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
amounts.complete();
|
|
34
|
+
return "";
|
|
35
|
+
}
|
|
36
|
+
}
|
package/peer/AssetPull.sol
CHANGED
|
@@ -25,7 +25,7 @@ abstract contract PeerAssetPull is PeerBase, PeerAssetPullHook {
|
|
|
25
25
|
uint internal immutable peerAssetPullId = peerId(NAME);
|
|
26
26
|
|
|
27
27
|
constructor() {
|
|
28
|
-
emit Peer(host, NAME, Schemas.Amount,
|
|
28
|
+
emit Peer(host, peerAssetPullId, NAME, Schemas.Amount, false);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/// @notice Execute the asset-pull peer call.
|
package/peer/Base.sol
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
2
|
pragma solidity ^0.8.33;
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { NodeCalls } from "../core/Calls.sol";
|
|
5
5
|
import { PeerEvent } from "../events/Peer.sol";
|
|
6
6
|
import { Ids, Selectors } from "../utils/Ids.sol";
|
|
7
7
|
|
|
@@ -18,11 +18,15 @@ function encodePeerCall(uint target, bytes calldata request) pure returns (bytes
|
|
|
18
18
|
|
|
19
19
|
/// @title PeerBase
|
|
20
20
|
/// @notice Abstract base for all rootzero peer contracts.
|
|
21
|
-
/// Peers handle inter-host
|
|
21
|
+
/// Peers handle inter-host operations and asset allow/deny management
|
|
22
22
|
/// between cooperating hosts. Access is restricted to trusted callers via `onlyPeer`.
|
|
23
|
-
abstract contract PeerBase is
|
|
24
|
-
/// @dev
|
|
23
|
+
abstract contract PeerBase is NodeCalls, PeerEvent {
|
|
24
|
+
/// @dev Thrown when the commander attempts to call a peer entrypoint directly.
|
|
25
|
+
error CommanderNotAllowed();
|
|
26
|
+
|
|
27
|
+
/// @dev Restrict execution to trusted callers, excluding the commander.
|
|
25
28
|
modifier onlyPeer() {
|
|
29
|
+
if (msg.sender == commander) revert CommanderNotAllowed();
|
|
26
30
|
enforceCaller(msg.sender);
|
|
27
31
|
_;
|
|
28
32
|
}
|
package/peer/DenyAssets.sol
CHANGED
|
@@ -16,7 +16,7 @@ abstract contract PeerDenyAssets is PeerBase, DenyAssetsHook {
|
|
|
16
16
|
uint internal immutable peerDenyAssetsId = peerId(NAME);
|
|
17
17
|
|
|
18
18
|
constructor() {
|
|
19
|
-
emit Peer(host, NAME, Schemas.Asset,
|
|
19
|
+
emit Peer(host, peerDenyAssetsId, NAME, Schemas.Asset, false);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
/// @notice Execute the deny-assets peer call.
|
package/peer/Settle.sol
CHANGED
|
@@ -3,7 +3,7 @@ pragma solidity ^0.8.33;
|
|
|
3
3
|
|
|
4
4
|
import { PeerBase } from "./Base.sol";
|
|
5
5
|
import { TransferHook } from "../commands/Transfer.sol";
|
|
6
|
-
import { Cursors, Cur,
|
|
6
|
+
import { Cursors, Cur, Schemas } from "../Cursors.sol";
|
|
7
7
|
|
|
8
8
|
using Cursors for Cur;
|
|
9
9
|
|
|
@@ -16,7 +16,7 @@ abstract contract PeerSettle is PeerBase, TransferHook {
|
|
|
16
16
|
uint internal immutable peerSettleId = peerId(NAME);
|
|
17
17
|
|
|
18
18
|
constructor() {
|
|
19
|
-
emit Peer(host, NAME, Schemas.Transaction,
|
|
19
|
+
emit Peer(host, peerSettleId, NAME, Schemas.Transaction, false);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
/// @notice Execute the peer-settle call.
|
|
@@ -24,8 +24,7 @@ abstract contract PeerSettle is PeerBase, TransferHook {
|
|
|
24
24
|
(Cur memory state, , ) = cursor(request, 1);
|
|
25
25
|
|
|
26
26
|
while (state.i < state.bound) {
|
|
27
|
-
|
|
28
|
-
transfer(value);
|
|
27
|
+
transfer(state.unpackTxValue());
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
state.complete();
|
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)`,
|