@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/core/Host.sol
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
2
|
pragma solidity ^0.8.33;
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {AccessDenied, CallerAccess, CommanderAccess} from "./Access.sol";
|
|
5
|
+
import {Runtime} from "./Runtime.sol";
|
|
6
|
+
import {Annotate} from "../commands/admin/Annotate.sol";
|
|
5
7
|
import {Appoint} from "../commands/admin/Appoint.sol";
|
|
6
8
|
import {Authorize} from "../commands/admin/Authorize.sol";
|
|
7
9
|
import {Dismiss} from "../commands/admin/Dismiss.sol";
|
|
8
10
|
import {Unauthorize} from "../commands/admin/Unauthorize.sol";
|
|
9
11
|
import {ExecutePayable} from "../commands/admin/Execute.sol";
|
|
10
|
-
import {Label} from "../commands/admin/Label.sol";
|
|
11
12
|
import {Revoke} from "../guards/Revoke.sol";
|
|
12
13
|
import {IntroductionEvent} from "../events/Introduction.sol";
|
|
14
|
+
import {Accounts} from "../utils/Accounts.sol";
|
|
13
15
|
import {Nodes} from "../utils/Nodes.sol";
|
|
14
16
|
|
|
15
17
|
/// @title IHostIntroduction
|
|
@@ -21,6 +23,55 @@ interface IHostIntroduction {
|
|
|
21
23
|
function introduce(uint peer, uint blocknum) external;
|
|
22
24
|
}
|
|
23
25
|
|
|
26
|
+
/// @title HostIntroduction
|
|
27
|
+
/// @notice Shared deployment-time introduction behavior for rootzero hosts.
|
|
28
|
+
/// Calls a deployed commander during construction without adding an inbound
|
|
29
|
+
/// introduction endpoint to the inheriting host.
|
|
30
|
+
abstract contract HostIntroduction is Runtime {
|
|
31
|
+
/// @param cmdr Commander address to introduce this host to when it is a deployed contract.
|
|
32
|
+
/// @dev Deployment reverts if a contract commander does not accept `introduce(uint,uint)`.
|
|
33
|
+
constructor(address cmdr) {
|
|
34
|
+
if (cmdr == address(0) || cmdr == address(this) || cmdr.code.length == 0) return;
|
|
35
|
+
introduceTo(Nodes.toHost(cmdr));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/// @notice Introduce this host to the contract address embedded in a local EVM node ID.
|
|
39
|
+
/// @dev Accepts host and endpoint IDs such as commands, ports, queries, and guards.
|
|
40
|
+
/// Reverts when `node` is not local or embeds the zero address.
|
|
41
|
+
/// @param node Local EVM node ID whose underlying contract receives the introduction.
|
|
42
|
+
function introduceTo(uint node) internal {
|
|
43
|
+
IHostIntroduction(Nodes.addr(node)).introduce(host, block.number);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// @title CommandHost
|
|
48
|
+
/// @notice Minimal host base for commander-only command execution.
|
|
49
|
+
/// Does not include admin commands, peer authorization, guardians, inbound
|
|
50
|
+
/// introductions, generic execution, or a native-token receive function.
|
|
51
|
+
/// Commands using trusted `NodeCalls` must separately compose a `TrustAccess` policy.
|
|
52
|
+
abstract contract CommandHost is CommanderAccess, CallerAccess, HostIntroduction {
|
|
53
|
+
/// @dev Thrown when a commander-only host is deployed without an external commander.
|
|
54
|
+
error InvalidCommander();
|
|
55
|
+
|
|
56
|
+
/// @param cmdr Nonzero address allowed to invoke hosted commands.
|
|
57
|
+
constructor(address cmdr) CommanderAccess(cmdr) HostIntroduction(cmdr) {
|
|
58
|
+
if (cmdr == address(0)) revert InvalidCommander();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function enforceCaller(address caller) internal view virtual override returns (address) {
|
|
62
|
+
return enforceCommander(caller);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// @title Admins
|
|
68
|
+
/// @notice Optional bundle of the default host administration commands.
|
|
69
|
+
abstract contract Admins is Annotate, ExecutePayable, Authorize, Unauthorize {}
|
|
70
|
+
|
|
71
|
+
/// @title Guardians
|
|
72
|
+
/// @notice Optional bundle for guardian management and the default revoke guard.
|
|
73
|
+
abstract contract Guardians is Appoint, Dismiss, Revoke {}
|
|
74
|
+
|
|
24
75
|
/// @title Host
|
|
25
76
|
/// @notice Abstract base contract for rootzero host implementations.
|
|
26
77
|
/// Inherits admin command support (authorize, unauthorize, label, executePayable),
|
|
@@ -28,30 +79,21 @@ interface IHostIntroduction {
|
|
|
28
79
|
/// optionally introduces itself to a commander host at deployment.
|
|
29
80
|
/// Accepts native ETH payments via the `receive` function.
|
|
30
81
|
abstract contract Host is
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
Appoint,
|
|
35
|
-
Dismiss,
|
|
36
|
-
Label,
|
|
37
|
-
ExecutePayable,
|
|
82
|
+
Admins,
|
|
83
|
+
Guardians,
|
|
84
|
+
HostIntroduction,
|
|
38
85
|
IntroductionEvent,
|
|
39
86
|
IHostIntroduction
|
|
40
87
|
{
|
|
41
|
-
/// @param cmdr Commander address;
|
|
88
|
+
/// @param cmdr Commander address; used by the composed access capabilities.
|
|
42
89
|
/// If `cmdr` is a deployed contract, the host calls `introduce`
|
|
43
90
|
/// on it during construction.
|
|
44
|
-
constructor(address cmdr)
|
|
45
|
-
if (cmdr == address(0) || cmdr == address(this) || cmdr.code.length == 0) return;
|
|
46
|
-
introduceTo(Nodes.toHost(cmdr));
|
|
47
|
-
}
|
|
91
|
+
constructor(address cmdr) CommanderAccess(cmdr) HostIntroduction(cmdr) {}
|
|
48
92
|
|
|
49
|
-
/// @notice
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
function introduceTo(uint node) internal {
|
|
54
|
-
IHostIntroduction(Nodes.addr(node)).introduce(host, block.number);
|
|
93
|
+
/// @notice Assert that `caller` may invoke commands on a peer-aware host.
|
|
94
|
+
function enforceCaller(address caller) internal view virtual override returns (address) {
|
|
95
|
+
if (caller == address(0) || !isTrustedCaller(caller)) revert AccessDenied();
|
|
96
|
+
return caller;
|
|
55
97
|
}
|
|
56
98
|
|
|
57
99
|
/// @notice Record a host introduction claim.
|
|
@@ -59,7 +101,7 @@ abstract contract Host is
|
|
|
59
101
|
/// @param peer Host node ID being introduced.
|
|
60
102
|
/// @param blocknum Block number at which the host was deployed.
|
|
61
103
|
function introduce(uint peer, uint blocknum) external {
|
|
62
|
-
emit Introduction(host, Nodes.matchHost(peer, msg.sender), blocknum);
|
|
104
|
+
emit Introduction(host, Nodes.matchHost(peer, msg.sender), Accounts.toUser(tx.origin), blocknum);
|
|
63
105
|
}
|
|
64
106
|
|
|
65
107
|
/// @notice Accept native ETH transfers (e.g. from command value flows).
|
package/docs/Schema.md
CHANGED
|
@@ -27,12 +27,23 @@ For example, the standard `amount` alias uses the key derived from `#amount`
|
|
|
27
27
|
and the schema body `{ bytes32 asset, uint amount }`. Custom block keys do not
|
|
28
28
|
have to be keccak-derived. They
|
|
29
29
|
are opaque `bytes4` tags and only need to be unique in the context where they are
|
|
30
|
-
used. A host can publish the meaning of a custom key
|
|
30
|
+
used. A host can publish the meaning of a custom key as an annotation:
|
|
31
31
|
|
|
32
32
|
```solidity
|
|
33
|
-
event
|
|
33
|
+
event Annotation(uint indexed entity, bytes data);
|
|
34
|
+
#schema { uint spec, #string as body, bytes32 name }
|
|
34
35
|
```
|
|
35
36
|
|
|
37
|
+
Annotation merge behavior is defined by the annotation block type rather than
|
|
38
|
+
by the `Annotation` event. A `#schema` annotation is identified by its entity
|
|
39
|
+
and the block key encoded in `spec`: distinct keys accumulate, while the latest
|
|
40
|
+
trusted claim for the same key replaces the earlier one. Other annotation types
|
|
41
|
+
may define additive, historical, or explicitly revocable behavior instead.
|
|
42
|
+
|
|
43
|
+
The standard `#action { uint action }` annotation assigns one primary semantic
|
|
44
|
+
action to an entity. The latest trusted value replaces the previous value, and
|
|
45
|
+
`Actions.None` clears the classification.
|
|
46
|
+
|
|
36
47
|
For example, a host-specific payment block can use a small literal, the command
|
|
37
48
|
selector, or any other chosen `bytes4` value as long as that key is not
|
|
38
49
|
overloaded in the relevant host/schema context.
|
|
@@ -155,7 +166,7 @@ top-level structure.
|
|
|
155
166
|
|
|
156
167
|
## Field Aliases
|
|
157
168
|
|
|
158
|
-
Block aliases are published in `
|
|
169
|
+
Block aliases are published in `#schema` annotations. Field aliases are presentation
|
|
159
170
|
metadata for tooling. They do not change payload layout or runtime keys.
|
|
160
171
|
|
|
161
172
|
```txt
|
|
@@ -306,8 +317,8 @@ invalid in any path segment.
|
|
|
306
317
|
- `#list`: generic list wrapper emitted by `many`
|
|
307
318
|
|
|
308
319
|
Custom input shapes should define their own context-local block spec and publish
|
|
309
|
-
it with a `
|
|
310
|
-
and publish that spec:
|
|
320
|
+
it with a `#schema` annotation. Endpoint contracts can use `schema(...)` to
|
|
321
|
+
construct and publish that spec:
|
|
311
322
|
|
|
312
323
|
```solidity
|
|
313
324
|
uint input = schema(1, 64, 64, 64, "{ bytes32 asset, uint amount }", bytes32(0));
|
|
@@ -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
|
+
/// @notice Emitted to attach encoded annotation blocks to an entity.
|
|
7
|
+
/// @dev `data` is a protocol block stream. A single block is the conventional
|
|
8
|
+
/// case, but related annotations may be emitted together. Annotations are claims
|
|
9
|
+
/// by the emitting contract, and each block key defines its annotation type.
|
|
10
|
+
/// Consumers process events in log order and blocks in stream order, then apply
|
|
11
|
+
/// the identity and merge rules defined by each annotation type. The event does
|
|
12
|
+
/// not impose a universal replacement policy: a type may replace, accumulate,
|
|
13
|
+
/// preserve history, or define its own revocation convention.
|
|
14
|
+
abstract contract AnnotationEvent is EventEmitter {
|
|
15
|
+
string private constant ABI = "event Annotation(uint indexed entity, bytes data)";
|
|
16
|
+
|
|
17
|
+
/// @param entity Entity being annotated.
|
|
18
|
+
/// @param data Encoded annotation block stream, conventionally containing one block.
|
|
19
|
+
event Annotation(uint indexed entity, bytes data);
|
|
20
|
+
|
|
21
|
+
constructor() {
|
|
22
|
+
emit EventAbi(ABI);
|
|
23
|
+
}
|
|
24
|
+
}
|
package/events/Guardian.sol
CHANGED
|
@@ -3,12 +3,12 @@ pragma solidity ^0.8.33;
|
|
|
3
3
|
|
|
4
4
|
import { EventEmitter } from "./Emitter.sol";
|
|
5
5
|
|
|
6
|
-
/// @notice Emitted when a
|
|
6
|
+
/// @notice Emitted when a user account's guardian role changes on a host.
|
|
7
7
|
abstract contract GuardianEvent is EventEmitter {
|
|
8
8
|
string private constant ABI = "event Guardian(uint indexed host, bytes32 account, bool active)";
|
|
9
9
|
|
|
10
10
|
/// @param host Host node ID where the guardian change occurred.
|
|
11
|
-
/// @param account
|
|
11
|
+
/// @param account User account ID assigned or removed as a guardian.
|
|
12
12
|
/// @param active True if the guardian is enabled, false if revoked.
|
|
13
13
|
event Guardian(uint indexed host, bytes32 account, bool active);
|
|
14
14
|
|
package/events/Introduction.sol
CHANGED
|
@@ -5,12 +5,13 @@ import { EventEmitter } from "./Emitter.sol";
|
|
|
5
5
|
|
|
6
6
|
/// @notice Emitted when a host introduces itself to another host.
|
|
7
7
|
abstract contract IntroductionEvent is EventEmitter {
|
|
8
|
-
string private constant ABI = "event Introduction(uint indexed host, uint peer, uint blocknum)";
|
|
8
|
+
string private constant ABI = "event Introduction(uint indexed host, uint peer, bytes32 origin, uint blocknum)";
|
|
9
9
|
|
|
10
10
|
/// @param host Host node ID receiving the introduction.
|
|
11
11
|
/// @param peer Host node ID of the introducing contract.
|
|
12
|
+
/// @param origin Transaction-origin address encoded as a chain-agnostic user account.
|
|
12
13
|
/// @param blocknum Block number at which the host was deployed.
|
|
13
|
-
event Introduction(uint indexed host, uint peer, uint blocknum);
|
|
14
|
+
event Introduction(uint indexed host, uint peer, bytes32 origin, uint blocknum);
|
|
14
15
|
|
|
15
16
|
constructor() {
|
|
16
17
|
emit EventAbi(ABI);
|
package/execution/Execution.sol
CHANGED
|
@@ -166,6 +166,14 @@ library Executions {
|
|
|
166
166
|
exec.decoders = cur.seekAbs(end);
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
/// @notice Require the active execution decoder to be at absolute position `abs`.
|
|
170
|
+
/// @dev The most recent lane-aware decoder operation determines the active lane.
|
|
171
|
+
/// @param exec Execution whose active decoder position is validated.
|
|
172
|
+
/// @param abs Expected absolute position.
|
|
173
|
+
function expectAbs(Execution memory exec, uint abs) internal pure {
|
|
174
|
+
exec.decoders.expectAbs(abs);
|
|
175
|
+
}
|
|
176
|
+
|
|
169
177
|
/// @notice Consume a LIST block and return a cursor scoped to its payload.
|
|
170
178
|
/// @param exec Execution whose decoder is advanced.
|
|
171
179
|
/// @param lane Decoder lane containing the LIST block.
|
|
@@ -520,6 +528,21 @@ library Executions {
|
|
|
520
528
|
exec.decoders = cur.seekAbs(end);
|
|
521
529
|
}
|
|
522
530
|
|
|
531
|
+
/// @notice Decode and consume one ANNOTATION block from `lane`.
|
|
532
|
+
/// @param exec Execution whose decoder is advanced.
|
|
533
|
+
/// @param lane Decoder lane to consume.
|
|
534
|
+
/// @return entity Decoded entity identifier.
|
|
535
|
+
/// @return data Decoded annotation block stream.
|
|
536
|
+
function unpackAnnotation(
|
|
537
|
+
Execution memory exec,
|
|
538
|
+
uint8 lane
|
|
539
|
+
) internal pure returns (uint entity, bytes calldata data) {
|
|
540
|
+
uint cur = exec.decoders.select(lane);
|
|
541
|
+
uint end;
|
|
542
|
+
(entity, data, end) = Blocks.unpackAnnotation(cur.absolute());
|
|
543
|
+
exec.decoders = cur.seekAbs(end);
|
|
544
|
+
}
|
|
545
|
+
|
|
523
546
|
/// @notice Decode and consume one CONTEXT block from `lane`.
|
|
524
547
|
/// @param exec Execution whose decoder is advanced.
|
|
525
548
|
/// @param lane Decoder lane to consume.
|
|
@@ -573,17 +596,16 @@ library Executions {
|
|
|
573
596
|
/// @notice Decode and consume one LABEL block from `lane`.
|
|
574
597
|
/// @param exec Execution whose decoder is advanced.
|
|
575
598
|
/// @param lane Decoder lane to consume.
|
|
576
|
-
/// @return id Decoded node identifier.
|
|
577
599
|
/// @return namespace Decoded label namespace.
|
|
578
600
|
/// @return name Decoded label text.
|
|
579
601
|
function unpackLabel(
|
|
580
602
|
Execution memory exec,
|
|
581
603
|
uint8 lane
|
|
582
|
-
) internal pure returns (
|
|
604
|
+
) internal pure returns (bytes32 namespace, string memory name) {
|
|
583
605
|
uint cur = exec.decoders.select(lane);
|
|
584
606
|
uint abs = cur.absolute();
|
|
585
607
|
uint end;
|
|
586
|
-
(
|
|
608
|
+
(namespace, name, end) = Blocks.unpackLabel(abs);
|
|
587
609
|
exec.decoders = cur.seekAbs(end);
|
|
588
610
|
}
|
|
589
611
|
|
|
@@ -956,13 +978,12 @@ library Executions {
|
|
|
956
978
|
|
|
957
979
|
/// @notice Append a LABEL block to execution output.
|
|
958
980
|
/// @param exec Execution receiving the block.
|
|
959
|
-
/// @param id Node identifier to encode.
|
|
960
981
|
/// @param namespace Label namespace to encode.
|
|
961
982
|
/// @param name Label text to encode.
|
|
962
|
-
function outputLabel(Execution memory exec,
|
|
963
|
-
uint size = Sizes.
|
|
983
|
+
function outputLabel(Execution memory exec, bytes32 namespace, string memory name) internal pure {
|
|
984
|
+
uint size = Sizes.B32 + Sizes.Header + bytes(name).length;
|
|
964
985
|
uint i = reserve(exec, size);
|
|
965
|
-
Blocks.writeLabel(exec.output, i,
|
|
986
|
+
Blocks.writeLabel(exec.output, i, namespace, name);
|
|
966
987
|
}
|
|
967
988
|
|
|
968
989
|
/// @notice Append a SCHEMA block to execution output.
|
package/guards/Base.sol
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
2
|
pragma solidity ^0.8.33;
|
|
3
3
|
|
|
4
|
-
import {AccessControl} from "../core/Access.sol";
|
|
5
4
|
import {EndpointBase} from "../core/Endpoint.sol";
|
|
5
|
+
import {GuardianAccess} from "../core/Access.sol";
|
|
6
6
|
import {Specs} from "../codec/Specs.sol";
|
|
7
7
|
import {Nodes} from "../utils/Nodes.sol";
|
|
8
8
|
import {Selectors} from "../utils/Selectors.sol";
|
|
@@ -10,10 +10,10 @@ import {Selectors} from "../utils/Selectors.sol";
|
|
|
10
10
|
/// @title GuardBase
|
|
11
11
|
/// @notice Abstract base for guardian-only direct host actions.
|
|
12
12
|
/// Guard actions are non-payable direct calls with no command context, state, or response.
|
|
13
|
-
abstract contract GuardBase is
|
|
13
|
+
abstract contract GuardBase is GuardianAccess, EndpointBase {
|
|
14
14
|
/// @dev Restrict execution to active guardian addresses.
|
|
15
15
|
modifier onlyGuardian() {
|
|
16
|
-
|
|
16
|
+
enforceGuardian(msg.sender);
|
|
17
17
|
_;
|
|
18
18
|
}
|
|
19
19
|
|
package/package.json
CHANGED
package/ports/Base.sol
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
pragma solidity ^0.8.33;
|
|
3
3
|
|
|
4
4
|
import { NodeCalls } from "../core/Calls.sol";
|
|
5
|
+
import { NodeAccess } from "../core/Access.sol";
|
|
5
6
|
import { Specs } from "../codec/Specs.sol";
|
|
6
7
|
import { EndpointBase } from "../core/Endpoint.sol";
|
|
7
8
|
import { Nodes } from "../utils/Nodes.sol";
|
|
@@ -12,14 +13,11 @@ import { Descriptors } from "../codec/Descriptors.sol";
|
|
|
12
13
|
/// @notice Abstract base for peer-facing rootzero ports.
|
|
13
14
|
/// Ports handle inter-host operations between cooperating hosts.
|
|
14
15
|
/// Access is restricted to trusted peer callers via `onlyPeer`.
|
|
15
|
-
abstract contract PortBase is NodeCalls, EndpointBase {
|
|
16
|
-
/// @dev Thrown when the commander attempts to call a port entrypoint directly.
|
|
17
|
-
error CommanderNotAllowed();
|
|
16
|
+
abstract contract PortBase is NodeCalls, NodeAccess, EndpointBase {
|
|
18
17
|
|
|
19
18
|
/// @dev Restrict execution to trusted callers, excluding the commander.
|
|
20
19
|
modifier onlyPeer() {
|
|
21
|
-
|
|
22
|
-
enforceCaller(msg.sender);
|
|
20
|
+
enforcePeer(msg.sender);
|
|
23
21
|
_;
|
|
24
22
|
}
|
|
25
23
|
|
|
@@ -44,7 +42,21 @@ abstract contract PortBase is NodeCalls, EndpointBase {
|
|
|
44
42
|
uint output,
|
|
45
43
|
bool funded
|
|
46
44
|
) internal returns (uint id, uint descriptor) {
|
|
45
|
+
descriptor = Descriptors.create(Specs.Empty, input, output, 0, funded ? Descriptors.Funded : 0);
|
|
46
|
+
return port(name, descriptor);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// @notice Publish an already constructed port descriptor and default label.
|
|
50
|
+
/// @param name Port entrypoint name and default label. It must exactly
|
|
51
|
+
/// match the Solidity port function name used by the canonical ABI.
|
|
52
|
+
/// @param descriptor Packed port endpoint descriptor.
|
|
53
|
+
/// @return id Port node ID.
|
|
54
|
+
/// @return published Published endpoint descriptor.
|
|
55
|
+
function port(
|
|
56
|
+
string memory name,
|
|
57
|
+
uint descriptor
|
|
58
|
+
) internal returns (uint id, uint published) {
|
|
47
59
|
id = Nodes.toPort(Selectors.port(name), address(this));
|
|
48
|
-
|
|
60
|
+
published = endpoint(id, name, descriptor);
|
|
49
61
|
}
|
|
50
62
|
}
|
package/ports/Settle.sol
CHANGED
|
@@ -5,17 +5,21 @@ import {PortBase} from "./Base.sol";
|
|
|
5
5
|
import {Settlement} from "../core/Settlement.sol";
|
|
6
6
|
import {Specs} from "../Codec.sol";
|
|
7
7
|
import {Execution, Executions, Lanes} from "../execution/Execution.sol";
|
|
8
|
+
import {Action} from "../annotations/Action.sol";
|
|
9
|
+
import {Actions} from "../utils/Actions.sol";
|
|
8
10
|
|
|
9
11
|
using Executions for Execution;
|
|
10
12
|
|
|
11
13
|
/// @title PortSettle
|
|
12
14
|
/// @notice Port that consumes peer-supplied TRANSACTION blocks through debit and credit hooks.
|
|
13
15
|
/// Each TRANSACTION block calls `debitAccount` for `from` and `creditAccount` for `to`.
|
|
14
|
-
abstract contract PortSettle is PortBase, Settlement {
|
|
16
|
+
abstract contract PortSettle is PortBase, Settlement, Action {
|
|
15
17
|
uint private immutable descriptor;
|
|
16
18
|
|
|
17
19
|
constructor() {
|
|
18
|
-
|
|
20
|
+
uint id;
|
|
21
|
+
(id, descriptor) = port("portSettle", Specs.Transaction, Specs.Empty, false);
|
|
22
|
+
action(id, Actions.Settle);
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
/// @notice Execute the port-settle call.
|
package/queries/Base.sol
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
pragma solidity ^0.8.33;
|
|
3
3
|
|
|
4
4
|
import { EndpointBase } from "../core/Endpoint.sol";
|
|
5
|
+
import { Descriptors } from "../codec/Descriptors.sol";
|
|
5
6
|
import { Specs } from "../codec/Specs.sol";
|
|
6
7
|
import { Nodes } from "../utils/Nodes.sol";
|
|
7
8
|
import { Selectors } from "../utils/Selectors.sol";
|
|
@@ -24,7 +25,21 @@ abstract contract QueryBase is EndpointBase {
|
|
|
24
25
|
uint input,
|
|
25
26
|
uint output
|
|
26
27
|
) internal returns (uint id, uint descriptor) {
|
|
28
|
+
descriptor = Descriptors.create(Specs.Empty, input, output, 0, 0);
|
|
29
|
+
return query(name, descriptor);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// @notice Publish an already constructed query descriptor and default label.
|
|
33
|
+
/// @param name Query entrypoint name and default label. It must exactly
|
|
34
|
+
/// match the Solidity query function name used by the canonical ABI.
|
|
35
|
+
/// @param descriptor Packed query endpoint descriptor.
|
|
36
|
+
/// @return id Query node ID.
|
|
37
|
+
/// @return published Published endpoint descriptor.
|
|
38
|
+
function query(
|
|
39
|
+
string memory name,
|
|
40
|
+
uint descriptor
|
|
41
|
+
) internal returns (uint id, uint published) {
|
|
27
42
|
id = Nodes.toQuery(Selectors.query(name), address(this));
|
|
28
|
-
|
|
43
|
+
published = endpoint(id, name, descriptor);
|
|
29
44
|
}
|
|
30
45
|
}
|
package/utils/Accounts.sol
CHANGED
|
@@ -10,7 +10,6 @@ import {ensureAddr, isFamily, toLocalBase, toUnspecifiedBase} from "./Utils.sol"
|
|
|
10
10
|
///
|
|
11
11
|
/// Account IDs embed a 4-byte type tag in bits [255:224]:
|
|
12
12
|
/// - `Admin` — chain-local EVM address in bits [191:32]
|
|
13
|
-
/// - `Guardian` — chain-local EVM address in bits [191:32]
|
|
14
13
|
/// - `User` — chain-agnostic EVM address in bits [191:32]
|
|
15
14
|
///
|
|
16
15
|
/// If the first byte is zero, the account is an opaque
|
|
@@ -26,8 +25,6 @@ library Accounts {
|
|
|
26
25
|
uint24 constant Family = (uint24(Layout.Evm) << 8) | uint24(Layout.Account);
|
|
27
26
|
/// @dev Full 4-byte type prefix for admin accounts (chain-local EVM address).
|
|
28
27
|
uint32 constant Admin = (uint32(Layout.Evm) << 16) | (uint32(Layout.Account) << 8) | uint32(Layout.Admin);
|
|
29
|
-
/// @dev Full 4-byte type prefix for guardian accounts (chain-local EVM address).
|
|
30
|
-
uint32 constant Guardian = (uint32(Layout.Evm) << 16) | (uint32(Layout.Account) << 8) | uint32(Layout.Guardian);
|
|
31
28
|
/// @dev Full 4-byte type prefix for user accounts (chain-agnostic EVM address).
|
|
32
29
|
uint32 constant User = (uint32(Layout.Evm) << 16) | (uint32(Layout.Account) << 8) | uint32(Layout.User);
|
|
33
30
|
|
|
@@ -53,11 +50,6 @@ library Accounts {
|
|
|
53
50
|
return prefix(account) == Admin;
|
|
54
51
|
}
|
|
55
52
|
|
|
56
|
-
/// @notice Return true if `account` is a guardian account.
|
|
57
|
-
function isGuardian(bytes32 account) internal pure returns (bool) {
|
|
58
|
-
return prefix(account) == Guardian;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
53
|
/// @notice Return true if `account` is a user account.
|
|
62
54
|
function isUser(bytes32 account) internal pure returns (bool) {
|
|
63
55
|
return prefix(account) == User;
|
|
@@ -87,14 +79,6 @@ library Accounts {
|
|
|
87
79
|
return value;
|
|
88
80
|
}
|
|
89
81
|
|
|
90
|
-
/// @notice Assert that `value` is a guardian account and return it unchanged.
|
|
91
|
-
/// @param value Account identifier to validate.
|
|
92
|
-
/// @return account The same `value` if it is a guardian account.
|
|
93
|
-
function guardian(bytes32 value) internal pure returns (bytes32 account) {
|
|
94
|
-
if (!isGuardian(value)) revert InvalidAccount();
|
|
95
|
-
return value;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
82
|
/// @notice Assert that `value` is a user account and return it unchanged.
|
|
99
83
|
/// @param value Account identifier to validate.
|
|
100
84
|
/// @return account The same `value` if it is a user account.
|
|
@@ -110,13 +94,6 @@ library Accounts {
|
|
|
110
94
|
return bytes32(toLocalBase(Admin) | (uint(uint160(account)) << 32));
|
|
111
95
|
}
|
|
112
96
|
|
|
113
|
-
/// @notice Encode an EVM address as a chain-local guardian account ID.
|
|
114
|
-
/// @param account EVM address to embed.
|
|
115
|
-
/// @return Guardian account ID bound to the current chain.
|
|
116
|
-
function toGuardian(address account) internal view returns (bytes32) {
|
|
117
|
-
return bytes32(toLocalBase(Guardian) | (uint(uint160(account)) << 32));
|
|
118
|
-
}
|
|
119
|
-
|
|
120
97
|
/// @notice Encode an EVM address as a chain-agnostic user account ID.
|
|
121
98
|
/// @param account EVM address to embed.
|
|
122
99
|
/// @return User account ID without a chain binding.
|
package/utils/Cursors.sol
CHANGED
|
@@ -20,9 +20,22 @@ struct Cur {
|
|
|
20
20
|
/// bits 96-111 groups
|
|
21
21
|
/// bits 112-119 flags (consumer-defined)
|
|
22
22
|
/// bits 120-127 tag
|
|
23
|
-
///
|
|
24
|
-
///
|
|
25
|
-
///
|
|
23
|
+
///
|
|
24
|
+
/// A cursor is one 128-bit value with this layout. A pair is a 256-bit value
|
|
25
|
+
/// containing a lower cursor in bits 0-127 and an optional higher cursor in
|
|
26
|
+
/// bits 128-255. The lower cursor is the active cursor: navigation and
|
|
27
|
+
/// inspection operations target it, and `select` swaps a requested tagged
|
|
28
|
+
/// cursor into that position while preserving the pair.
|
|
29
|
+
///
|
|
30
|
+
/// A mark is a standalone cursor value used as an immutable positional
|
|
31
|
+
/// reference. It retains the cursor's offset, length, groups, flags, and tag,
|
|
32
|
+
/// but may carry a different `i`. A mark has no intrinsic boundary or movement
|
|
33
|
+
/// semantics; callers may later use its position for comparison, validation,
|
|
34
|
+
/// seeking, or another operation. Because it has the ordinary single-cursor
|
|
35
|
+
/// layout, existing positional decoding and absolute-position rules also apply
|
|
36
|
+
/// to marks. A zero mark identifies the empty cursor at position zero; `before`
|
|
37
|
+
/// therefore treats it as already reached.
|
|
38
|
+
///
|
|
26
39
|
/// The zero word represents an absent cursor.
|
|
27
40
|
library Cursors {
|
|
28
41
|
/// @dev A cursor position exceeds its logical length.
|
|
@@ -37,8 +50,8 @@ library Cursors {
|
|
|
37
50
|
/// @dev Paired cursors must have different identity tags.
|
|
38
51
|
error DuplicateTag(uint8 tag);
|
|
39
52
|
|
|
40
|
-
/// @dev Neither packed cursor
|
|
41
|
-
error
|
|
53
|
+
/// @dev Neither packed cursor matches the requested identity.
|
|
54
|
+
error MissingCursor();
|
|
42
55
|
|
|
43
56
|
// Creation and sources
|
|
44
57
|
|
|
@@ -97,6 +110,14 @@ library Cursors {
|
|
|
97
110
|
len = uint32(cur >> 64);
|
|
98
111
|
}
|
|
99
112
|
|
|
113
|
+
/// @notice Return the active cursor without its position.
|
|
114
|
+
/// @dev Ignores the upper cursor when `cur` is a pair.
|
|
115
|
+
/// @param cur Packed cursor or cursor pair.
|
|
116
|
+
/// @return The lower cursor's offset, length, groups, flags, and tag.
|
|
117
|
+
function frame(uint cur) internal pure returns (uint) {
|
|
118
|
+
return clear32(uint128(cur), 0);
|
|
119
|
+
}
|
|
120
|
+
|
|
100
121
|
/// @notice Return the absolute position of the lower cursor as `offset + i`.
|
|
101
122
|
/// @dev Performs no bounds check.
|
|
102
123
|
/// @param cur Packed cursor or cursor pair.
|
|
@@ -144,6 +165,13 @@ library Cursors {
|
|
|
144
165
|
if (uint32(cur) != i) revert UnexpectedPosition();
|
|
145
166
|
}
|
|
146
167
|
|
|
168
|
+
/// @notice Require the lower cursor to be positioned at absolute position `abs`.
|
|
169
|
+
/// @param cur Packed cursor or cursor pair.
|
|
170
|
+
/// @param abs Expected absolute position.
|
|
171
|
+
function expectAbs(uint cur, uint abs) internal pure {
|
|
172
|
+
if (absolute(cur) != abs) revert UnexpectedPosition();
|
|
173
|
+
}
|
|
174
|
+
|
|
147
175
|
/// @notice Return whether the lower cursor contains `flag`.
|
|
148
176
|
/// @param cur Packed cursor or cursor pair.
|
|
149
177
|
/// @param flag Consumer-defined flag bit or bit set.
|
|
@@ -279,7 +307,38 @@ library Cursors {
|
|
|
279
307
|
if (uint8(cur >> 120) == expected) return cur;
|
|
280
308
|
|
|
281
309
|
updated = swap(cur);
|
|
282
|
-
if (uint8(updated >> 120) != expected) revert
|
|
310
|
+
if (uint8(updated >> 120) != expected) revert MissingCursor();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Marks
|
|
314
|
+
|
|
315
|
+
/// @notice Select the live cursor whose frame matches the active cursor in `mark`.
|
|
316
|
+
/// @dev Only the lower 128 bits of `mark` are considered. The returned value
|
|
317
|
+
/// preserves the cursor pair and places the matching cursor in the lower half.
|
|
318
|
+
/// A zero frame may select an empty cursor lane.
|
|
319
|
+
/// @param cur Packed cursor or cursor pair to search.
|
|
320
|
+
/// @param mark Cursor-shaped positional reference to match.
|
|
321
|
+
/// @return located Cursor pair with the matching cursor active.
|
|
322
|
+
function locate(uint cur, uint mark) internal pure returns (uint located) {
|
|
323
|
+
uint expected = frame(mark);
|
|
324
|
+
if (frame(cur) == expected) return cur;
|
|
325
|
+
|
|
326
|
+
located = swap(cur);
|
|
327
|
+
if (frame(located) != expected) revert MissingCursor();
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/// @notice Return whether a matched cursor is positioned before `mark`.
|
|
331
|
+
/// @dev Returns false at the mark and reverts after it. Only the active lower
|
|
332
|
+
/// cursor in `mark` participates in the comparison. A zero mark represents
|
|
333
|
+
/// the already-reached position of an empty cursor.
|
|
334
|
+
/// @param cur Packed cursor or cursor pair containing the marked cursor.
|
|
335
|
+
/// @param mark Cursor-shaped positional reference.
|
|
336
|
+
/// @return Whether the live cursor position precedes the marked position.
|
|
337
|
+
function before(uint cur, uint mark) internal pure returns (bool) {
|
|
338
|
+
uint i = uint32(locate(cur, mark));
|
|
339
|
+
uint target = uint32(mark);
|
|
340
|
+
if (i > target) revert OutOfBounds();
|
|
341
|
+
return i < target;
|
|
283
342
|
}
|
|
284
343
|
|
|
285
344
|
// Consumption
|
package/utils/Layout.sol
CHANGED
|
@@ -40,8 +40,6 @@ library Layout {
|
|
|
40
40
|
|
|
41
41
|
/// @dev Admin account — chain-local, backed by an EVM address.
|
|
42
42
|
uint8 constant Admin = 0x01;
|
|
43
|
-
/// @dev Guardian account — chain-local, backed by an EVM address.
|
|
44
|
-
uint8 constant Guardian = 0x02;
|
|
45
43
|
/// @dev User account — chain-agnostic, backed by an EVM address.
|
|
46
44
|
uint8 constant User = 0x03;
|
|
47
45
|
// -------------------------------------------------------------------------
|
package/commands/admin/Label.sol
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
|
-
pragma solidity ^0.8.33;
|
|
3
|
-
|
|
4
|
-
import {AdminBase, Execution, Executions, Lanes, Specs} from "./Base.sol";
|
|
5
|
-
using Executions for Execution;
|
|
6
|
-
|
|
7
|
-
/// @title Label
|
|
8
|
-
/// @notice Admin command that publishes namespaced labels for node IDs.
|
|
9
|
-
/// Each LABEL block in the input emits one `Labeled` event. Only callable by
|
|
10
|
-
/// the admin account.
|
|
11
|
-
abstract contract Label is AdminBase {
|
|
12
|
-
uint private immutable descriptor;
|
|
13
|
-
|
|
14
|
-
constructor() {
|
|
15
|
-
(, descriptor) = command("label", Specs.Empty, Specs.Label, Specs.Empty, 0, false, true);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/// @notice Publish each LABEL block in the admin input.
|
|
19
|
-
/// @param input LABEL block stream.
|
|
20
|
-
/// @return Empty output state.
|
|
21
|
-
/// @return Empty transaction stream.
|
|
22
|
-
function label(
|
|
23
|
-
bytes32 account,
|
|
24
|
-
bytes calldata,
|
|
25
|
-
bytes calldata input
|
|
26
|
-
) external onlyAdmin(account) returns (bytes memory, bytes memory) {
|
|
27
|
-
Execution memory exec = openInput(input, descriptor, 0);
|
|
28
|
-
|
|
29
|
-
while (exec.more()) {
|
|
30
|
-
(uint node, bytes32 namespace, string memory name) = exec.unpackLabel(Lanes.Input);
|
|
31
|
-
emit Labeled(node, namespace, name);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return close(exec, account);
|
|
35
|
-
}
|
|
36
|
-
}
|