@rootzero/contracts 1.14.0 → 1.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -0
- package/Core.sol +6 -3
- package/Endpoints.sol +1 -2
- package/Events.sol +1 -2
- package/README.md +36 -7
- package/annotations/Action.sol +17 -0
- package/annotations/Label.sol +23 -0
- package/annotations/Schema.sol +53 -0
- package/codec/Blocks.sol +48 -11
- package/codec/Decoders.sol +15 -3
- package/codec/Keys.sol +6 -2
- package/codec/Schema.sol +7 -2
- package/codec/Specs.sol +24 -2
- package/codec/Writers.sol +2 -4
- package/commands/Base.sol +36 -32
- package/commands/Burn.sol +6 -2
- package/commands/Deposit.sol +10 -4
- package/commands/Payout.sol +6 -2
- package/commands/Withdraw.sol +6 -2
- package/commands/admin/Annotate.sol +36 -0
- package/commands/admin/Appoint.sol +4 -3
- package/commands/admin/Base.sol +8 -1
- package/commands/admin/Dismiss.sol +4 -3
- package/commands/admin/Execute.sol +2 -1
- package/core/Access.sol +91 -49
- package/core/Calls.sol +27 -22
- package/core/Endpoint.sol +34 -46
- package/core/Host.sol +63 -21
- package/docs/Schema.md +16 -5
- package/events/Annotation.sol +24 -0
- package/events/Guardian.sol +2 -2
- package/events/Introduction.sol +3 -2
- package/execution/Execution.sol +28 -7
- package/guards/Base.sol +3 -3
- package/package.json +1 -1
- package/ports/Base.sol +18 -6
- package/ports/Settle.sol +6 -2
- package/queries/Base.sol +16 -1
- package/utils/Accounts.sol +0 -23
- package/utils/Cursors.sol +65 -6
- package/utils/Layout.sol +0 -2
- package/commands/admin/Label.sol +0 -36
- package/commands/admin/Schemas.sol +0 -36
- package/events/Labeled.sol +0 -21
- package/events/Schema.sol +0 -23
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,48 @@
|
|
|
3
3
|
Until the protocol reaches integration-stable status, minor versions may include
|
|
4
4
|
breaking API changes. Breaking changes are called out explicitly.
|
|
5
5
|
|
|
6
|
+
## 1.15.0
|
|
7
|
+
|
|
8
|
+
### Breaking Changes
|
|
9
|
+
|
|
10
|
+
- Replaced the monolithic `AccessControl` base with the composable
|
|
11
|
+
`CommanderAccess`, `AdminAccess`, `NodeAccess`, `GuardianAccess`,
|
|
12
|
+
`CallerAccess`, and `TrustAccess` capabilities. `CommandBase` now requires a
|
|
13
|
+
concrete caller policy and no longer inherits outbound `NodeCalls`.
|
|
14
|
+
- Split host composition into the commander-only `CommandHost`, the advanced
|
|
15
|
+
`Host`, and the optional `Admins` and `Guardians` feature bundles. Commands
|
|
16
|
+
that use trusted outbound calls must now inherit `NodeCalls` directly and
|
|
17
|
+
compose a `TrustAccess` implementation.
|
|
18
|
+
- Removed the guardian account subtype. Guardians are now ordinary user
|
|
19
|
+
accounts assigned a host-local role, so previously encoded guardian account
|
|
20
|
+
IDs are not compatible.
|
|
21
|
+
- Replaced the `Labeled` and `Schema` discovery events and their dedicated admin
|
|
22
|
+
commands with typed blocks in the generic `Annotation` event and the
|
|
23
|
+
`annotate` admin command.
|
|
24
|
+
- Added the origin user account to `Introduction`, changing its event signature
|
|
25
|
+
to `Introduction(uint indexed host, uint peer, bytes32 origin, uint blocknum)`.
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- Added opt-in `Label`, `Schema`, and `Action` annotation mixins together with
|
|
30
|
+
canonical `#label`, `#schema`, `#annotation`, and `#action` codec support.
|
|
31
|
+
- Added semantic action annotations to deposit, payable deposit, withdrawal,
|
|
32
|
+
burn, payout, and port settlement endpoints.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- Enabled the Solidity optimizer with 200 runs and pinned release testing to
|
|
37
|
+
the Cancun EVM target, the minimum target supporting the codec's `MCOPY` use.
|
|
38
|
+
- Guardians can revoke node access but remain unable to grant it; admin
|
|
39
|
+
commands continue to require both the immutable commander caller and its
|
|
40
|
+
derived admin account.
|
|
41
|
+
|
|
42
|
+
### Upgrade Compatibility
|
|
43
|
+
|
|
44
|
+
- Existing deployments are not upgradeable and this release does not preserve
|
|
45
|
+
storage layout or guardian mapping keys for proxy upgrades. Deploy fresh host
|
|
46
|
+
contracts when adopting this version.
|
|
47
|
+
|
|
6
48
|
## 1.14.0
|
|
7
49
|
|
|
8
50
|
### Breaking Changes
|
package/Core.sol
CHANGED
|
@@ -4,12 +4,15 @@ pragma solidity ^0.8.33;
|
|
|
4
4
|
// Aggregator: re-exports the core host, runtime, access, ledger, settlement, pipeline, node-call, and validation layer.
|
|
5
5
|
// Import this file to bring the full rootzero host base layer into scope.
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { Action } from "./annotations/Action.sol";
|
|
8
|
+
import { Label } from "./annotations/Label.sol";
|
|
9
|
+
import { Schema } from "./annotations/Schema.sol";
|
|
10
|
+
import { AdminAccess, CallerAccess, CommanderAccess, GuardianAccess, NodeAccess, TrustAccess } from "./core/Access.sol";
|
|
8
11
|
import { Balances, InsufficientFunds } from "./core/Balances.sol";
|
|
9
12
|
import { Escrows, InsufficientEscrow } from "./core/Escrows.sol";
|
|
10
13
|
import { NativeAsset, Runtime } from "./core/Runtime.sol";
|
|
11
|
-
import { Host, IHostIntroduction } from "./core/Host.sol";
|
|
12
|
-
import { CommandCalls, FailedCall, NodeCalls, PortCalls } from "./core/Calls.sol";
|
|
14
|
+
import { Admins, CommandHost, Guardians, Host, HostIntroduction, IHostIntroduction } from "./core/Host.sol";
|
|
15
|
+
import { CommandCalls, FailedCall, NodeCalls, PortCalls, RawNodeCalls } from "./core/Calls.sol";
|
|
13
16
|
import { EndpointBase } from "./core/Endpoint.sol";
|
|
14
17
|
import { Pipeline } from "./core/Pipeline.sol";
|
|
15
18
|
import { Budget, Budgets } from "./execution/Budget.sol";
|
package/Endpoints.sol
CHANGED
|
@@ -23,13 +23,12 @@ import { Withdraw, WithdrawHook } from "./commands/Withdraw.sol";
|
|
|
23
23
|
import { AdminBase } from "./commands/admin/Base.sol";
|
|
24
24
|
import { AllowAssets, AllowAssetsHook } from "./commands/admin/AllowAssets.sol";
|
|
25
25
|
import { Allowance, AllowanceHook } from "./commands/admin/Allowance.sol";
|
|
26
|
+
import { Annotate } from "./commands/admin/Annotate.sol";
|
|
26
27
|
import { Appoint } from "./commands/admin/Appoint.sol";
|
|
27
28
|
import { Authorize } from "./commands/admin/Authorize.sol";
|
|
28
29
|
import { DenyAssets, DenyAssetsHook } from "./commands/admin/DenyAssets.sol";
|
|
29
30
|
import { Dismiss } from "./commands/admin/Dismiss.sol";
|
|
30
31
|
import { ExecutePayable } from "./commands/admin/Execute.sol";
|
|
31
|
-
import { Label } from "./commands/admin/Label.sol";
|
|
32
|
-
import { PublishSchema } from "./commands/admin/Schemas.sol";
|
|
33
32
|
import { Unauthorize } from "./commands/admin/Unauthorize.sol";
|
|
34
33
|
|
|
35
34
|
// Port endpoints
|
package/Events.sol
CHANGED
|
@@ -4,6 +4,7 @@ pragma solidity ^0.8.33;
|
|
|
4
4
|
// Aggregator: re-exports all event contracts.
|
|
5
5
|
// Import this file to get access to every event emitter in one import.
|
|
6
6
|
|
|
7
|
+
import { AnnotationEvent } from "./events/Annotation.sol";
|
|
7
8
|
import { AssetEvent, AssetStatusEvent } from "./events/Asset.sol";
|
|
8
9
|
import { Actions } from "./utils/Actions.sol";
|
|
9
10
|
import { BalanceEvent } from "./events/Balance.sol";
|
|
@@ -16,12 +17,10 @@ import { RecoveredEvent } from "./events/Recovered.sol";
|
|
|
16
17
|
import { EventEmitter } from "./events/Emitter.sol";
|
|
17
18
|
import { GuardianEvent } from "./events/Guardian.sol";
|
|
18
19
|
import { IntroductionEvent } from "./events/Introduction.sol";
|
|
19
|
-
import { LabeledEvent } from "./events/Labeled.sol";
|
|
20
20
|
import { LockedEvent } from "./events/Locked.sol";
|
|
21
21
|
import { NodeEvent } from "./events/Node.sol";
|
|
22
22
|
import { RootedEvent } from "./events/Rooted.sol";
|
|
23
23
|
import { RouteEvent } from "./events/Route.sol";
|
|
24
|
-
import { SchemaEvent } from "./events/Schema.sol";
|
|
25
24
|
import { SpentEvent } from "./events/Spent.sol";
|
|
26
25
|
import { UndeliveredEvent } from "./events/Undelivered.sol";
|
|
27
26
|
import { UnlockedEvent } from "./events/Unlocked.sol";
|
package/README.md
CHANGED
|
@@ -27,18 +27,18 @@ npx create-rootzero@latest my-app
|
|
|
27
27
|
npm install @rootzero/contracts
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
A minimal host composes
|
|
31
|
-
implements their policy hooks:
|
|
30
|
+
A minimal commander-only host composes `CommandHost` with the endpoints it
|
|
31
|
+
needs and implements their policy hooks:
|
|
32
32
|
|
|
33
33
|
```solidity
|
|
34
34
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
35
35
|
pragma solidity ^0.8.33;
|
|
36
36
|
|
|
37
|
-
import {
|
|
37
|
+
import { CommandHost, Balances } from "@rootzero/contracts/Core.sol";
|
|
38
38
|
import { Deposit } from "@rootzero/contracts/Endpoints.sol";
|
|
39
39
|
|
|
40
|
-
contract ExampleHost is
|
|
41
|
-
constructor(address
|
|
40
|
+
contract ExampleHost is CommandHost, Balances, Deposit {
|
|
41
|
+
constructor(address commander) CommandHost(commander) {}
|
|
42
42
|
|
|
43
43
|
function deposit(bytes32 account, bytes32 asset, uint amount) internal override {
|
|
44
44
|
uint balance = creditTo(account, asset, amount);
|
|
@@ -47,6 +47,29 @@ contract ExampleHost is Host, Balances, Deposit {
|
|
|
47
47
|
}
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
+
`CommandHost` requires a nonzero commander and accepts command calls only from
|
|
51
|
+
that address. It has no built-in admin commands, peer registry, guardians,
|
|
52
|
+
inbound introduction endpoint, generic execution command, or native-token
|
|
53
|
+
receive function. Use `Host` instead when the application needs those advanced
|
|
54
|
+
facilities; its commands accept the commander, the host itself, and explicitly
|
|
55
|
+
authorized host callers.
|
|
56
|
+
|
|
57
|
+
Both host types introduce themselves during deployment when the commander is a
|
|
58
|
+
contract. That commander must implement `introduce(uint,uint)` and accept the
|
|
59
|
+
call, otherwise deployment reverts. EOA commanders do not receive an
|
|
60
|
+
introduction call.
|
|
61
|
+
|
|
62
|
+
Host contracts are designed for fresh deployment rather than proxy upgrades.
|
|
63
|
+
Releases may change inheritance storage layout, immutable configuration, and
|
|
64
|
+
encoded identity formats; storage compatibility across versions is not
|
|
65
|
+
supported.
|
|
66
|
+
|
|
67
|
+
Commands using trusted outbound `NodeCalls` also require a `TrustAccess`
|
|
68
|
+
implementation. The advanced `Host` supplies one through its composed node access, while
|
|
69
|
+
`CommandHost` deliberately does not. A minimal host can explicitly compose a
|
|
70
|
+
custom trust policy, or a command that intentionally targets arbitrary nodes
|
|
71
|
+
can inherit `RawNodeCalls` instead.
|
|
72
|
+
|
|
50
73
|
Deploy it with your own address as commander and you can call its commands
|
|
51
74
|
directly. A input is a run of binary blocks — here, a single `#amount` block
|
|
52
75
|
asking to deposit an asset (the encoders are a few lines each; see
|
|
@@ -164,7 +187,8 @@ Structured EVM IDs use:
|
|
|
164
187
|
where `type` packs `[uint16 representation][uint8 category][uint8 subtype]`. A
|
|
165
188
|
structured ID announces what it is (an account, an asset, a node) and which
|
|
166
189
|
chain it lives on, and the payload usually embeds the underlying address. User
|
|
167
|
-
accounts are chain-agnostic
|
|
190
|
+
accounts are chain-agnostic, while admin accounts are chain-local. Guardians
|
|
191
|
+
are normal user accounts assigned a host-specific role.
|
|
168
192
|
Assets are unique IDs in the same single-word form as accounts and nodes.
|
|
169
193
|
Nodes are hosts, commands, ports, queries, and guards.
|
|
170
194
|
|
|
@@ -186,13 +210,18 @@ bytes32 opaque = Ids.toKeccak(preimage); // 0x00-prefixed opaque ID
|
|
|
186
210
|
|
|
187
211
|
A host is one contract assembled from mixins. The base `Host` brings access
|
|
188
212
|
control and the admin surface (authorize, unauthorize, appoint, dismiss,
|
|
189
|
-
|
|
213
|
+
annotate, executePayable) plus the guardian `revoke` action; you add the
|
|
190
214
|
endpoints you need and the policy hooks they require. Keeping a ledger is
|
|
191
215
|
optional: the `Balances` mixin provides one, but a host can just as well
|
|
192
216
|
implement commands that hold no persistent state in the host at all —
|
|
193
217
|
forwarding funds elsewhere, or operating only on the state threaded through a
|
|
194
218
|
pipeline.
|
|
195
219
|
|
|
220
|
+
The built-in surface is also available as two independent feature bundles:
|
|
221
|
+
`Admins` provides annotate, authorize, unauthorize, and executePayable;
|
|
222
|
+
`Guardians` provides appoint, dismiss, and revoke. The full `Host` composes
|
|
223
|
+
both, while smaller hosts can inherit either bundle separately.
|
|
224
|
+
|
|
196
225
|
Trust is explicit and minimal. Each host has an immutable **commander**
|
|
197
226
|
address fixed at construction, from which its **admin account** is derived.
|
|
198
227
|
Other contracts become callers only when their node ID is authorized into the
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
|
+
pragma solidity ^0.8.33;
|
|
3
|
+
|
|
4
|
+
import {AnnotationEvent} from "../events/Annotation.sol";
|
|
5
|
+
import {Blocks} from "../codec/Blocks.sol";
|
|
6
|
+
|
|
7
|
+
/// @title Action
|
|
8
|
+
/// @notice Emits a primary semantic action annotation for an entity.
|
|
9
|
+
/// @dev For a trusted emitter, the latest action replaces the earlier value.
|
|
10
|
+
abstract contract Action is AnnotationEvent {
|
|
11
|
+
/// @notice Attach a primary semantic action to `entity`.
|
|
12
|
+
/// @param entity Entity receiving the action annotation.
|
|
13
|
+
/// @param value Canonical action identifier, such as a value from `Actions`.
|
|
14
|
+
function action(uint entity, uint value) internal virtual {
|
|
15
|
+
emit Annotation(entity, Blocks.action(value));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
|
+
pragma solidity ^0.8.33;
|
|
3
|
+
|
|
4
|
+
import {AnnotationEvent} from "../events/Annotation.sol";
|
|
5
|
+
import {Blocks} from "../codec/Blocks.sol";
|
|
6
|
+
|
|
7
|
+
/// @title Label
|
|
8
|
+
/// @notice Emits standard label annotation blocks for entities.
|
|
9
|
+
/// @dev A label is identified by its entity and namespace. For a trusted
|
|
10
|
+
/// emitter, the latest label in a namespace replaces the earlier value.
|
|
11
|
+
abstract contract Label is AnnotationEvent {
|
|
12
|
+
/// @notice Attach a human-readable namespaced label to `entity`.
|
|
13
|
+
/// @param entity Entity receiving the label annotation.
|
|
14
|
+
/// @param namespace Label namespace.
|
|
15
|
+
/// @param name Human-readable name within the namespace.
|
|
16
|
+
function label(
|
|
17
|
+
uint entity,
|
|
18
|
+
bytes32 namespace,
|
|
19
|
+
string memory name
|
|
20
|
+
) internal virtual {
|
|
21
|
+
emit Annotation(entity, Blocks.label(namespace, name));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
|
+
pragma solidity ^0.8.33;
|
|
3
|
+
|
|
4
|
+
import {Blocks} from "../codec/Blocks.sol";
|
|
5
|
+
import {Specs} from "../codec/Specs.sol";
|
|
6
|
+
import {AnnotationEvent} from "../events/Annotation.sol";
|
|
7
|
+
import {Runtime} from "../core/Runtime.sol";
|
|
8
|
+
|
|
9
|
+
/// @title Schema
|
|
10
|
+
/// @notice Emits standard block-schema annotations for the current host.
|
|
11
|
+
/// @dev Schema annotations accumulate for distinct block keys. For a trusted
|
|
12
|
+
/// emitter, the latest schema for the same block key replaces the earlier claim.
|
|
13
|
+
abstract contract Schema is Runtime, AnnotationEvent {
|
|
14
|
+
/// @notice Construct and publish a context-local block specification.
|
|
15
|
+
/// @param key Context-local key value.
|
|
16
|
+
/// @param min Minimum accepted payload length.
|
|
17
|
+
/// @param max Maximum accepted payload length; zero means unbounded.
|
|
18
|
+
/// @param hint Initial per-block payload capacity.
|
|
19
|
+
/// @param body Schema DSL string describing the block payload body.
|
|
20
|
+
/// @param name Schema alias name, or zero for unnamed schemas.
|
|
21
|
+
/// @return spec The context-local block specification.
|
|
22
|
+
function schema(
|
|
23
|
+
uint32 key,
|
|
24
|
+
uint32 min,
|
|
25
|
+
uint32 max,
|
|
26
|
+
uint32 hint,
|
|
27
|
+
string memory body,
|
|
28
|
+
bytes32 name
|
|
29
|
+
) internal returns (uint spec) {
|
|
30
|
+
spec = Specs.create(key, min, max, hint);
|
|
31
|
+
return schema(spec, body, name);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// @notice Construct and publish an exact-size context-local block specification.
|
|
35
|
+
/// @param key Context-local key value.
|
|
36
|
+
/// @param size Exact payload length and initial per-block payload capacity.
|
|
37
|
+
/// @param body Schema DSL string describing the block payload body.
|
|
38
|
+
/// @param name Schema alias name, or zero for unnamed schemas.
|
|
39
|
+
/// @return spec The context-local block specification.
|
|
40
|
+
function schema(uint32 key, uint32 size, string memory body, bytes32 name) internal returns (uint spec) {
|
|
41
|
+
return schema(Specs.create(key, size), body, name);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// @notice Publish an already constructed block specification for the current host.
|
|
45
|
+
/// @param spec Packed block specification.
|
|
46
|
+
/// @param body Schema DSL string describing the block payload body.
|
|
47
|
+
/// @param name Schema alias name, or zero for unnamed schemas.
|
|
48
|
+
/// @return The published block specification.
|
|
49
|
+
function schema(uint spec, string memory body, bytes32 name) internal returns (uint) {
|
|
50
|
+
emit Annotation(host, Blocks.schema(spec, body, name));
|
|
51
|
+
return spec;
|
|
52
|
+
}
|
|
53
|
+
}
|
package/codec/Blocks.sol
CHANGED
|
@@ -754,20 +754,18 @@ library Blocks {
|
|
|
754
754
|
/// block size and ensure the encoded payload length fits in uint32.
|
|
755
755
|
/// @param dst Destination buffer.
|
|
756
756
|
/// @param i Relative write position.
|
|
757
|
-
/// @param id Node identifier.
|
|
758
757
|
/// @param namespace Label namespace.
|
|
759
758
|
/// @param name Label text.
|
|
760
|
-
function writeLabel(bytes memory dst, uint i,
|
|
761
|
-
uint len =
|
|
759
|
+
function writeLabel(bytes memory dst, uint i, bytes32 namespace, string memory name) internal pure {
|
|
760
|
+
uint len = 32 + Sizes.Header + bytes(name).length;
|
|
762
761
|
uint key = uint32(Keys.Label);
|
|
763
762
|
uint stringkey = uint32(Keys.String);
|
|
764
763
|
assembly ("memory-safe") {
|
|
765
764
|
let p := add(add(dst, 0x20), i)
|
|
766
765
|
mstore(p, or(shl(224, key), shl(192, len)))
|
|
767
|
-
mstore(add(p, 0x08),
|
|
768
|
-
mstore(add(p, 0x28), namespace)
|
|
766
|
+
mstore(add(p, 0x08), namespace)
|
|
769
767
|
|
|
770
|
-
let q := add(p,
|
|
768
|
+
let q := add(p, 0x28)
|
|
771
769
|
let namelen := mload(name)
|
|
772
770
|
mstore(q, or(shl(224, stringkey), shl(192, namelen)))
|
|
773
771
|
mcopy(add(q, 0x08), add(name, 0x20), namelen)
|
|
@@ -1345,6 +1343,23 @@ library Blocks {
|
|
|
1345
1343
|
|
|
1346
1344
|
// One fixed word
|
|
1347
1345
|
|
|
1346
|
+
/// @notice Decode one ANNOTATION block and its nested block stream.
|
|
1347
|
+
/// @param abs Absolute block position.
|
|
1348
|
+
/// @return entity Decoded entity identifier.
|
|
1349
|
+
/// @return stream Decoded annotation block stream.
|
|
1350
|
+
/// @return end Absolute position after the block.
|
|
1351
|
+
function unpackAnnotation(
|
|
1352
|
+
uint abs
|
|
1353
|
+
) internal pure returns (uint entity, bytes calldata stream, uint end) {
|
|
1354
|
+
uint limit;
|
|
1355
|
+
(abs, limit) = expect(abs, Specs.Annotation);
|
|
1356
|
+
assembly ("memory-safe") {
|
|
1357
|
+
entity := calldataload(abs)
|
|
1358
|
+
}
|
|
1359
|
+
(stream, end) = unpackBytes(abs + 32);
|
|
1360
|
+
if (end != limit) revert InvalidBlock();
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1348
1363
|
/// @notice Decode one CONTEXT block and all nested byte blocks.
|
|
1349
1364
|
/// @param abs Absolute block position.
|
|
1350
1365
|
/// @return account Decoded account identifier.
|
|
@@ -1444,19 +1459,17 @@ library Blocks {
|
|
|
1444
1459
|
|
|
1445
1460
|
/// @notice Decode one LABEL block and its nested name.
|
|
1446
1461
|
/// @param abs Absolute block position.
|
|
1447
|
-
/// @return id Decoded node identifier.
|
|
1448
1462
|
/// @return namespace Decoded label namespace.
|
|
1449
1463
|
/// @return name Decoded label text.
|
|
1450
1464
|
/// @return end Absolute position after the block.
|
|
1451
|
-
function unpackLabel(uint abs) internal pure returns (
|
|
1465
|
+
function unpackLabel(uint abs) internal pure returns (bytes32 namespace, string memory name, uint end) {
|
|
1452
1466
|
uint limit;
|
|
1453
1467
|
(abs, limit) = expect(abs, Specs.Label);
|
|
1454
1468
|
assembly ("memory-safe") {
|
|
1455
|
-
|
|
1456
|
-
namespace := calldataload(add(abs, 0x20))
|
|
1469
|
+
namespace := calldataload(abs)
|
|
1457
1470
|
}
|
|
1458
1471
|
bytes calldata value;
|
|
1459
|
-
(value, end) = unpackString(abs +
|
|
1472
|
+
(value, end) = unpackString(abs + 32);
|
|
1460
1473
|
if (end != limit) revert InvalidBlock();
|
|
1461
1474
|
name = string(value);
|
|
1462
1475
|
}
|
|
@@ -1532,6 +1545,30 @@ library Blocks {
|
|
|
1532
1545
|
return create(Keys.String, bytes(value));
|
|
1533
1546
|
}
|
|
1534
1547
|
|
|
1548
|
+
/// @notice Encode a LABEL block.
|
|
1549
|
+
/// @param namespace Label namespace.
|
|
1550
|
+
/// @param name Label text.
|
|
1551
|
+
/// @return Encoded LABEL block bytes.
|
|
1552
|
+
function label(bytes32 namespace, string memory name) internal pure returns (bytes memory) {
|
|
1553
|
+
return create(Keys.Label, bytes.concat(namespace, text(name)));
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
/// @notice Encode an ACTION annotation block.
|
|
1557
|
+
/// @param value Canonical semantic action identifier.
|
|
1558
|
+
/// @return Encoded ACTION block bytes.
|
|
1559
|
+
function action(uint value) internal pure returns (bytes memory) {
|
|
1560
|
+
return create(Keys.Action, bytes.concat(bytes32(value)));
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
/// @notice Encode a SCHEMA block.
|
|
1564
|
+
/// @param spec Block specification.
|
|
1565
|
+
/// @param body Schema body.
|
|
1566
|
+
/// @param name Schema name.
|
|
1567
|
+
/// @return Encoded SCHEMA block bytes.
|
|
1568
|
+
function schema(uint spec, string memory body, bytes32 name) internal pure returns (bytes memory) {
|
|
1569
|
+
return create(Keys.Schema, bytes.concat(bytes32(spec), text(body), name));
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1535
1572
|
/// @notice Encode a BALANCE block.
|
|
1536
1573
|
/// @param asset Asset identifier.
|
|
1537
1574
|
/// @param amount Token amount.
|
package/codec/Decoders.sol
CHANGED
|
@@ -365,6 +365,19 @@ library Decoders {
|
|
|
365
365
|
cur.state = cur.state.seekAbs(end);
|
|
366
366
|
}
|
|
367
367
|
|
|
368
|
+
/// @notice Decode and consume one ANNOTATION block.
|
|
369
|
+
/// @param cur Cursor advanced past the block.
|
|
370
|
+
/// @return entity Decoded entity identifier.
|
|
371
|
+
/// @return data Decoded annotation block stream.
|
|
372
|
+
function unpackAnnotation(
|
|
373
|
+
Cur memory cur
|
|
374
|
+
) internal pure returns (uint entity, bytes calldata data) {
|
|
375
|
+
uint abs = cur.state.absolute();
|
|
376
|
+
uint end;
|
|
377
|
+
(entity, data, end) = Blocks.unpackAnnotation(abs);
|
|
378
|
+
cur.state = cur.state.seekAbs(end);
|
|
379
|
+
}
|
|
380
|
+
|
|
368
381
|
/// @notice Decode and consume one CONTEXT block.
|
|
369
382
|
/// @param cur Cursor advanced past the block.
|
|
370
383
|
/// @return account Decoded account identifier.
|
|
@@ -395,13 +408,12 @@ library Decoders {
|
|
|
395
408
|
|
|
396
409
|
/// @notice Decode and consume one LABEL block.
|
|
397
410
|
/// @param cur Cursor advanced past the block.
|
|
398
|
-
/// @return id Decoded node identifier.
|
|
399
411
|
/// @return namespace Decoded label namespace.
|
|
400
412
|
/// @return name Decoded label text.
|
|
401
|
-
function unpackLabel(Cur memory cur) internal pure returns (
|
|
413
|
+
function unpackLabel(Cur memory cur) internal pure returns (bytes32 namespace, string memory name) {
|
|
402
414
|
uint abs = cur.state.absolute();
|
|
403
415
|
uint end;
|
|
404
|
-
(
|
|
416
|
+
(namespace, name, end) = Blocks.unpackLabel(abs);
|
|
405
417
|
cur.state = cur.state.seekAbs(end);
|
|
406
418
|
}
|
|
407
419
|
|
package/codec/Keys.sol
CHANGED
|
@@ -5,7 +5,7 @@ pragma solidity ^0.8.33;
|
|
|
5
5
|
/// @notice Standard block type selectors for the rootzero block stream protocol.
|
|
6
6
|
/// Standard keys use the first 4 bytes of `keccak256("#name")` by convention.
|
|
7
7
|
/// Custom block keys only need to be unique in the context where they are used;
|
|
8
|
-
/// hosts may publish custom key meanings with
|
|
8
|
+
/// hosts may publish custom key meanings with `#schema` annotations.
|
|
9
9
|
library Keys {
|
|
10
10
|
/// @dev Empty / unset key.
|
|
11
11
|
bytes4 constant Empty = bytes4(0);
|
|
@@ -49,8 +49,12 @@ library Keys {
|
|
|
49
49
|
bytes4 constant Asset = bytes4(keccak256("#asset"));
|
|
50
50
|
/// @dev Node identifier - (uint id)
|
|
51
51
|
bytes4 constant Node = bytes4(keccak256("#node"));
|
|
52
|
-
/// @dev
|
|
52
|
+
/// @dev Entity label annotation - (bytes32 namespace, #string as name)
|
|
53
53
|
bytes4 constant Label = bytes4(keccak256("#label"));
|
|
54
|
+
/// @dev Entity annotations - (uint entity, #bytes as data)
|
|
55
|
+
bytes4 constant Annotation = bytes4(keccak256("#annotation"));
|
|
56
|
+
/// @dev Primary semantic action annotation - (uint action)
|
|
57
|
+
bytes4 constant Action = bytes4(keccak256("#action"));
|
|
54
58
|
/// @dev Block schema publication - (bytes4 key, #string as body, bytes32 name)
|
|
55
59
|
bytes4 constant Schema = bytes4(keccak256("#schema"));
|
|
56
60
|
|
package/codec/Schema.sol
CHANGED
|
@@ -36,7 +36,7 @@ pragma solidity ^0.8.33;
|
|
|
36
36
|
// - generic lists use the stable key derived from `#list`
|
|
37
37
|
// - standard keys are derived from block aliases, e.g. bytes4(keccak256("#amount"))
|
|
38
38
|
// - custom keys are opaque bytes4 tags and only need to be unique in their
|
|
39
|
-
// active context; use
|
|
39
|
+
// active context; use a `#schema` annotation to publish their meaning
|
|
40
40
|
// - see `docs/Schema.md` for the full working spec
|
|
41
41
|
//
|
|
42
42
|
// Pipeline state:
|
|
@@ -101,7 +101,12 @@ library Schemas {
|
|
|
101
101
|
string constant Dispatch = "{ uint portal, uint resources, #bytes as payload }";
|
|
102
102
|
string constant Context = "{ bytes32 account, #bytes as state, #bytes as input }";
|
|
103
103
|
string constant Recover = "{ uint handler, uint resources, bytes32 key, #bytes as witness }";
|
|
104
|
-
string constant
|
|
104
|
+
string constant Annotation = "{ uint entity, #bytes as data }";
|
|
105
|
+
|
|
106
|
+
// Annotation payloads
|
|
107
|
+
|
|
108
|
+
string constant Action = "{ uint action }";
|
|
109
|
+
string constant Label = "{ bytes32 namespace, #string as name }";
|
|
105
110
|
string constant Schema = "{ uint spec, #string as body, bytes32 name }";
|
|
106
111
|
}
|
|
107
112
|
|
package/codec/Specs.sol
CHANGED
|
@@ -54,6 +54,7 @@ library Specs {
|
|
|
54
54
|
uint private constant Exact96 = 96 * SizeFields;
|
|
55
55
|
uint private constant Exact128 = 128 * SizeFields;
|
|
56
56
|
uint private constant UnboundedHint128 = uint(128) << 136;
|
|
57
|
+
uint private constant UnboundedMin40Hint256 = (uint(40) << 192) | (uint(256) << 136);
|
|
57
58
|
uint private constant UnboundedMin72Hint256 = (uint(72) << 192) | (uint(256) << 136);
|
|
58
59
|
uint private constant UnboundedMin48Hint512 = (uint(48) << 192) | (uint(512) << 136);
|
|
59
60
|
uint private constant UnboundedMin104Hint256 = (uint(104) << 192) | (uint(256) << 136);
|
|
@@ -79,7 +80,9 @@ library Specs {
|
|
|
79
80
|
uint constant Call = uint(bytes32(Keys.Call)) | UnboundedMin72Hint256;
|
|
80
81
|
uint constant Asset = uint(bytes32(Keys.Asset)) | Exact32;
|
|
81
82
|
uint constant Node = uint(bytes32(Keys.Node)) | Exact32;
|
|
82
|
-
uint constant Label = uint(bytes32(Keys.Label)) |
|
|
83
|
+
uint constant Label = uint(bytes32(Keys.Label)) | UnboundedMin40Hint256;
|
|
84
|
+
uint constant Annotation = uint(bytes32(Keys.Annotation)) | UnboundedMin40Hint256;
|
|
85
|
+
uint constant Action = uint(bytes32(Keys.Action)) | Exact32;
|
|
83
86
|
uint constant Schema = uint(bytes32(Keys.Schema)) | UnboundedMin72Hint256;
|
|
84
87
|
|
|
85
88
|
uint constant Status = uint(bytes32(Keys.Status)) | Exact32;
|
|
@@ -96,12 +99,31 @@ library Specs {
|
|
|
96
99
|
/// @param hint Initial per-block payload capacity.
|
|
97
100
|
/// @return spec Packed block specification.
|
|
98
101
|
function create(bytes4 blockkey, uint32 min, uint32 max, uint32 hint) internal pure returns (uint spec) {
|
|
99
|
-
|
|
102
|
+
return create(uint32(blockkey), min, max, hint);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/// @notice Construct a block specification from its numeric key and encoded fields.
|
|
106
|
+
/// @param blockkey Numeric block key.
|
|
107
|
+
/// @param min Minimum accepted payload length.
|
|
108
|
+
/// @param max Maximum accepted payload length; zero means unbounded.
|
|
109
|
+
/// @param hint Initial per-block payload capacity.
|
|
110
|
+
/// @return spec Packed block specification.
|
|
111
|
+
function create(uint32 blockkey, uint32 min, uint32 max, uint32 hint) internal pure returns (uint spec) {
|
|
112
|
+
spec |= uint(blockkey) << 224;
|
|
100
113
|
spec |= uint(min) << 192;
|
|
101
114
|
spec |= uint(max) << 160;
|
|
102
115
|
spec |= uint(max24(hint)) << 136;
|
|
103
116
|
}
|
|
104
117
|
|
|
118
|
+
/// @notice Construct an exact-size block specification from a numeric key.
|
|
119
|
+
/// @dev Sets the minimum, maximum, and allocation hint to `size`.
|
|
120
|
+
/// @param blockkey Numeric block key.
|
|
121
|
+
/// @param size Exact payload length and initial per-block payload capacity.
|
|
122
|
+
/// @return spec Packed exact-size block specification.
|
|
123
|
+
function create(uint32 blockkey, uint32 size) internal pure returns (uint spec) {
|
|
124
|
+
return create(blockkey, size, size, size);
|
|
125
|
+
}
|
|
126
|
+
|
|
105
127
|
/// @notice Decode the block key and accepted payload range from `spec`.
|
|
106
128
|
/// @param spec Packed block specification.
|
|
107
129
|
/// @return blockkey Encoded block key.
|
package/codec/Writers.sol
CHANGED
|
@@ -448,18 +448,16 @@ library Writers {
|
|
|
448
448
|
|
|
449
449
|
/// @notice Append a LABEL block.
|
|
450
450
|
/// @param writer Destination writer.
|
|
451
|
-
/// @param id Node identifier to encode.
|
|
452
451
|
/// @param namespace Label namespace to encode.
|
|
453
452
|
/// @param name Label text to encode.
|
|
454
453
|
function appendLabel(
|
|
455
454
|
Writer memory writer,
|
|
456
|
-
uint id,
|
|
457
455
|
bytes32 namespace,
|
|
458
456
|
string memory name
|
|
459
457
|
) internal pure {
|
|
460
|
-
uint size = Sizes.
|
|
458
|
+
uint size = Sizes.B32 + Sizes.Header + bytes(name).length;
|
|
461
459
|
uint i = reserve(writer, size);
|
|
462
|
-
Blocks.writeLabel(writer.dst, i,
|
|
460
|
+
Blocks.writeLabel(writer.dst, i, namespace, name);
|
|
463
461
|
}
|
|
464
462
|
|
|
465
463
|
/// @notice Append a SCHEMA block.
|
package/commands/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 {CallerAccess} from "../core/Access.sol";
|
|
5
5
|
import {EndpointBase} from "../core/Endpoint.sol";
|
|
6
6
|
import {Blocks} from "../codec/Blocks.sol";
|
|
7
7
|
import {Specs} from "../codec/Specs.sol";
|
|
@@ -19,17 +19,10 @@ using Executions for Execution;
|
|
|
19
19
|
/// @title CommandBase
|
|
20
20
|
/// @notice Abstract base for all rootzero command contracts.
|
|
21
21
|
/// Provides access control modifiers and command endpoint metadata helpers.
|
|
22
|
-
abstract contract CommandBase is
|
|
22
|
+
abstract contract CommandBase is CallerAccess, EndpointBase, ReceivedEvent {
|
|
23
23
|
/// @dev Thrown when `onlyActive` finds that `deadline` has already passed.
|
|
24
24
|
error Expired();
|
|
25
25
|
|
|
26
|
-
/// @dev Restrict execution to the commander using the host's admin account.
|
|
27
|
-
modifier onlyAdmin(bytes32 account) {
|
|
28
|
-
if (account != admin) revert AccessDenied();
|
|
29
|
-
enforceCommander(msg.sender);
|
|
30
|
-
_;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
26
|
/// @dev Restrict execution to trusted callers.
|
|
34
27
|
modifier onlyCommand() {
|
|
35
28
|
enforceCaller(msg.sender);
|
|
@@ -43,26 +36,6 @@ abstract contract CommandBase is NodeCalls, EndpointBase, ReceivedEvent {
|
|
|
43
36
|
_;
|
|
44
37
|
}
|
|
45
38
|
|
|
46
|
-
/// @notice Close a command execution and refund unspent value to `account`.
|
|
47
|
-
/// @param exec Command execution to close.
|
|
48
|
-
/// @param account Account that should receive any unspent value.
|
|
49
|
-
/// @return output Final encoded output block stream.
|
|
50
|
-
/// @return transactions Final encoded transaction block stream.
|
|
51
|
-
function close(
|
|
52
|
-
Execution memory exec,
|
|
53
|
-
bytes32 account
|
|
54
|
-
) internal returns (bytes memory output, bytes memory transactions) {
|
|
55
|
-
if (exec.budget == 0 && Cursors.initial(exec.writers)) return ("", "");
|
|
56
|
-
|
|
57
|
-
output = close(exec);
|
|
58
|
-
uint amount = exec.refundValue(account, nativeAsset);
|
|
59
|
-
if (amount != 0) {
|
|
60
|
-
emit Received(account, nativeAsset, amount, Actions.Refund, 0);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
transactions = exec.finishTransactions();
|
|
64
|
-
}
|
|
65
|
-
|
|
66
39
|
/// @notice Publish command metadata and a default label.
|
|
67
40
|
/// @param name Command entrypoint name and default label. It must exactly
|
|
68
41
|
/// match the Solidity command function name used by the canonical ABI.
|
|
@@ -83,11 +56,22 @@ abstract contract CommandBase is NodeCalls, EndpointBase, ReceivedEvent {
|
|
|
83
56
|
bool funded,
|
|
84
57
|
bool admin
|
|
85
58
|
) internal returns (uint id, uint descriptor) {
|
|
86
|
-
|
|
87
|
-
uint8 flags;
|
|
59
|
+
uint8 flags = 0;
|
|
88
60
|
if (funded) flags |= Descriptors.Funded;
|
|
89
61
|
if (admin) flags |= Descriptors.Admin;
|
|
90
|
-
descriptor =
|
|
62
|
+
descriptor = Descriptors.create(state, input, output, transactions, flags);
|
|
63
|
+
return command(name, descriptor);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/// @notice Publish an already constructed command descriptor and default label.
|
|
67
|
+
/// @param name Command entrypoint name and default label. It must exactly
|
|
68
|
+
/// match the Solidity command function name used by the canonical ABI.
|
|
69
|
+
/// @param descriptor Packed command endpoint descriptor.
|
|
70
|
+
/// @return id Command node ID.
|
|
71
|
+
/// @return published Published endpoint descriptor.
|
|
72
|
+
function command(string memory name, uint descriptor) internal returns (uint id, uint published) {
|
|
73
|
+
id = Nodes.toCommand(Selectors.command(name), address(this));
|
|
74
|
+
published = endpoint(id, name, descriptor);
|
|
91
75
|
}
|
|
92
76
|
|
|
93
77
|
/// @notice Open a command state stream and return the expected output block count.
|
|
@@ -117,4 +101,24 @@ abstract contract CommandBase is NodeCalls, EndpointBase, ReceivedEvent {
|
|
|
117
101
|
) internal view returns (Execution memory exec) {
|
|
118
102
|
return Executions.open(state, input, descriptor, batches);
|
|
119
103
|
}
|
|
104
|
+
|
|
105
|
+
/// @notice Close a command execution and refund unspent value to `account`.
|
|
106
|
+
/// @param exec Command execution to close.
|
|
107
|
+
/// @param account Account that should receive any unspent value.
|
|
108
|
+
/// @return output Final encoded output block stream.
|
|
109
|
+
/// @return transactions Final encoded transaction block stream.
|
|
110
|
+
function close(
|
|
111
|
+
Execution memory exec,
|
|
112
|
+
bytes32 account
|
|
113
|
+
) internal returns (bytes memory output, bytes memory transactions) {
|
|
114
|
+
if (exec.budget == 0 && Cursors.initial(exec.writers)) return ("", "");
|
|
115
|
+
|
|
116
|
+
output = close(exec);
|
|
117
|
+
uint amount = exec.refundValue(account, nativeAsset);
|
|
118
|
+
if (amount != 0) {
|
|
119
|
+
emit Received(account, nativeAsset, amount, Actions.Refund, 0);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
transactions = exec.finishTransactions();
|
|
123
|
+
}
|
|
120
124
|
}
|