@rootzero/contracts 1.6.0 → 1.8.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 +54 -1
- package/Core.sol +1 -1
- package/Endpoints.sol +15 -15
- package/Events.sol +2 -1
- package/README.md +9 -9
- package/blocks/Cursors.sol +36 -49
- package/blocks/Keys.sol +5 -5
- package/blocks/Schema.sol +4 -2
- package/blocks/Writers.sol +0 -35
- package/commands/Deposit.sol +2 -2
- package/commands/Provision.sol +2 -2
- package/commands/Recover.sol +48 -0
- package/commands/Relay.sol +4 -4
- package/commands/admin/Execute.sol +2 -4
- package/core/Calls.sol +74 -38
- package/core/Commitments.sol +1 -1
- package/core/Payable.sol +15 -28
- package/core/Pipeline.sol +1 -1
- package/docs/Schema.md +13 -2
- package/events/Commitment.sol +4 -4
- package/events/Dispatch.sol +20 -0
- package/events/Port.sol +22 -0
- package/guards/Base.sol +0 -11
- package/package.json +1 -1
- package/ports/AllowAssets.sol +40 -0
- package/ports/Allowance.sol +37 -0
- package/ports/Base.sol +30 -0
- package/ports/Credit.sol +35 -0
- package/ports/Debit.sol +35 -0
- package/ports/DenyAssets.sol +40 -0
- package/{peer → ports}/Dispatch.sol +11 -15
- package/ports/Pipe.sol +39 -0
- package/{peer → ports}/Redeem.sol +11 -15
- package/ports/Settle.sol +37 -0
- package/queries/Base.sol +0 -11
- package/utils/Layout.sol +3 -3
- package/utils/Nodes.sol +21 -21
- package/utils/Value.sol +12 -5
- package/events/Peer.sol +0 -22
- package/peer/AllowAssets.sol +0 -44
- package/peer/Allowance.sol +0 -41
- package/peer/Base.sol +0 -41
- package/peer/Credit.sol +0 -39
- package/peer/Debit.sol +0 -39
- package/peer/DenyAssets.sol +0 -44
- package/peer/Pipe.sol +0 -44
- package/peer/Recover.sol +0 -51
- package/peer/Settle.sol +0 -41
package/core/Calls.sol
CHANGED
|
@@ -12,7 +12,7 @@ import {Nodes} from "../utils/Nodes.sol";
|
|
|
12
12
|
error FailedCall(address addr, bytes4 selector, bytes err);
|
|
13
13
|
|
|
14
14
|
/// @title NodeCalls
|
|
15
|
-
/// @notice Shared
|
|
15
|
+
/// @notice Shared low-level inter-node call helpers for contracts that can talk to other nodes.
|
|
16
16
|
abstract contract NodeCalls is AccessControl {
|
|
17
17
|
/// @notice Return the host node ID corresponding to the current caller.
|
|
18
18
|
/// @dev Encodes `msg.sender` as a host ID using the local-chain host layout.
|
|
@@ -21,64 +21,100 @@ abstract contract NodeCalls is AccessControl {
|
|
|
21
21
|
return Nodes.toHost(msg.sender);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
/// @notice
|
|
25
|
-
///
|
|
26
|
-
/// Reverts with `FailedCall` if the call is unsuccessful.
|
|
27
|
-
/// @param addr Contract address to call.
|
|
24
|
+
/// @notice Try a raw low-level call to another node and return whether it succeeded.
|
|
25
|
+
/// @param node Node ID of the callee.
|
|
28
26
|
/// @param value Native value to forward in wei.
|
|
29
27
|
/// @param data Encoded calldata to send.
|
|
30
|
-
/// @return
|
|
31
|
-
function
|
|
32
|
-
|
|
33
|
-
(success,
|
|
34
|
-
if (!success) revert FailedCall(addr, bytes4(data), out);
|
|
28
|
+
/// @return success True if the low-level call succeeded.
|
|
29
|
+
function tryRawCall(uint node, uint128 value, bytes memory data) internal returns (bool success) {
|
|
30
|
+
address addr = Nodes.addr(node);
|
|
31
|
+
(success, ) = payable(addr).call{value: value}(data);
|
|
35
32
|
}
|
|
36
33
|
|
|
37
|
-
/// @notice
|
|
38
|
-
///
|
|
39
|
-
///
|
|
40
|
-
/// @param addr Contract address to query.
|
|
34
|
+
/// @notice Try a trusted low-level call to another node and return whether it succeeded.
|
|
35
|
+
/// @param node Node ID of the callee.
|
|
36
|
+
/// @param value Native value to forward in wei.
|
|
41
37
|
/// @param data Encoded calldata to send.
|
|
42
|
-
/// @return
|
|
43
|
-
function
|
|
38
|
+
/// @return success True if the low-level call succeeded.
|
|
39
|
+
function tryTrustedCall(uint node, uint128 value, bytes memory data) internal returns (bool success) {
|
|
40
|
+
return tryRawCall(ensureTrusted(node), value, data);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/// @notice Make a raw low-level call to another node and revert when it fails.
|
|
44
|
+
/// @param node Node ID of the callee.
|
|
45
|
+
/// @param value Native value to forward in wei.
|
|
46
|
+
/// @param data Encoded calldata to send.
|
|
47
|
+
/// @return out Return data from the successful call.
|
|
48
|
+
function rawCall(uint node, uint128 value, bytes memory data) internal returns (bytes memory out) {
|
|
44
49
|
bool success;
|
|
45
|
-
|
|
50
|
+
address addr = Nodes.addr(node);
|
|
51
|
+
(success, out) = payable(addr).call{value: value}(data);
|
|
46
52
|
if (!success) revert FailedCall(addr, bytes4(data), out);
|
|
47
53
|
}
|
|
48
54
|
|
|
49
|
-
/// @notice Make a trusted call to another node
|
|
50
|
-
///
|
|
51
|
-
/// then issues a low-level call forwarding `value` ETH and `data`.
|
|
52
|
-
/// @param node Node ID of the callee (must be in the authorized set).
|
|
55
|
+
/// @notice Make a trusted low-level call to another node and revert when it fails.
|
|
56
|
+
/// @param node Node ID of the callee.
|
|
53
57
|
/// @param value Native value to forward in wei.
|
|
54
58
|
/// @param data Encoded calldata to send.
|
|
55
59
|
/// @return out Return data from the successful call.
|
|
56
|
-
function
|
|
57
|
-
ensureTrusted(node);
|
|
58
|
-
address addr = Nodes.addr(node);
|
|
59
|
-
return callAddr(addr, value, data);
|
|
60
|
+
function trustedCall(uint node, uint128 value, bytes memory data) internal returns (bytes memory out) {
|
|
61
|
+
return rawCall(ensureTrusted(node), value, data);
|
|
60
62
|
}
|
|
61
63
|
|
|
62
|
-
/// @notice Make a
|
|
63
|
-
///
|
|
64
|
-
/// then issues a low-level `staticcall` with `data`.
|
|
65
|
-
/// @param node Node ID of the callee (must be in the authorized set).
|
|
64
|
+
/// @notice Make a raw low-level read-only query to another node and revert when it fails.
|
|
65
|
+
/// @param node Node ID of the callee.
|
|
66
66
|
/// @param data Encoded calldata to send.
|
|
67
67
|
/// @return out Return data from the successful query.
|
|
68
|
-
function
|
|
69
|
-
|
|
68
|
+
function rawQuery(uint node, bytes memory data) internal view returns (bytes memory out) {
|
|
69
|
+
bool success;
|
|
70
70
|
address addr = Nodes.addr(node);
|
|
71
|
-
|
|
71
|
+
(success, out) = addr.staticcall(data);
|
|
72
|
+
if (!success) revert FailedCall(addr, bytes4(data), out);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// @notice Make a trusted low-level read-only query to another node and revert when it fails.
|
|
76
|
+
/// @param node Node ID of the callee.
|
|
77
|
+
/// @param data Encoded calldata to send.
|
|
78
|
+
/// @return out Return data from the successful query.
|
|
79
|
+
function trustedQuery(uint node, bytes memory data) internal view returns (bytes memory out) {
|
|
80
|
+
return rawQuery(ensureTrusted(node), data);
|
|
72
81
|
}
|
|
82
|
+
}
|
|
73
83
|
|
|
84
|
+
/// @title CommandCalls
|
|
85
|
+
/// @notice Trusted command-call helpers for contracts that route command nodes.
|
|
86
|
+
abstract contract CommandCalls is NodeCalls {
|
|
74
87
|
/// @notice Encode and call a trusted command node.
|
|
75
|
-
/// @param
|
|
88
|
+
/// @param command Command node ID embedding the target selector.
|
|
76
89
|
/// @param value Native value to forward in wei.
|
|
77
|
-
/// @param
|
|
90
|
+
/// @param account Command account identifier.
|
|
91
|
+
/// @param state Current command state block stream.
|
|
92
|
+
/// @param request Command input block stream.
|
|
78
93
|
/// @return Decoded command output block stream.
|
|
79
|
-
function callCommand(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
94
|
+
function callCommand(
|
|
95
|
+
uint command,
|
|
96
|
+
uint128 value,
|
|
97
|
+
bytes32 account,
|
|
98
|
+
bytes memory state,
|
|
99
|
+
bytes calldata request
|
|
100
|
+
) internal returns (bytes memory) {
|
|
101
|
+
bytes4 selector = Nodes.commandSelector(command);
|
|
102
|
+
bytes memory data = abi.encodeWithSelector(selector, CommandContext(account, state, request));
|
|
103
|
+
return abi.decode(trustedCall(command, value, data), (bytes));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/// @title PortCalls
|
|
108
|
+
/// @notice Trusted port-call helpers for contracts that route port nodes.
|
|
109
|
+
abstract contract PortCalls is NodeCalls {
|
|
110
|
+
/// @notice Encode and call a trusted port node.
|
|
111
|
+
/// @param port Port node ID embedding the target selector.
|
|
112
|
+
/// @param value Native value to forward in wei.
|
|
113
|
+
/// @param input Port input block stream.
|
|
114
|
+
/// @return Decoded port output block stream.
|
|
115
|
+
function callPort(uint port, uint128 value, bytes memory input) internal returns (bytes memory) {
|
|
116
|
+
bytes4 selector = Nodes.portSelector(port);
|
|
117
|
+
bytes memory data = abi.encodeWithSelector(selector, input);
|
|
118
|
+
return abi.decode(trustedCall(port, value, data), (bytes));
|
|
83
119
|
}
|
|
84
120
|
}
|
package/core/Commitments.sol
CHANGED
|
@@ -4,7 +4,7 @@ pragma solidity ^0.8.33;
|
|
|
4
4
|
import {CommitmentEvent} from "../events/Commitment.sol";
|
|
5
5
|
|
|
6
6
|
/// @title Commitments
|
|
7
|
-
/// @notice On-chain registry for
|
|
7
|
+
/// @notice On-chain registry for digest commitments.
|
|
8
8
|
abstract contract Commitments is CommitmentEvent {
|
|
9
9
|
/// @dev key -> committed digest.
|
|
10
10
|
mapping(bytes32 key => bytes32 digest) internal commitments;
|
package/core/Payable.sol
CHANGED
|
@@ -5,16 +5,14 @@ import {Budget, Values} from "../utils/Value.sol";
|
|
|
5
5
|
|
|
6
6
|
/// @title Payable
|
|
7
7
|
/// @notice Abstract mixin for entrypoints that accept native value (`msg.value`).
|
|
8
|
-
/// Provides
|
|
9
|
-
/// mutable budget after execution completes.
|
|
8
|
+
/// Provides shared helpers for mutable native-value budgets.
|
|
10
9
|
abstract contract Payable {
|
|
11
10
|
/// @dev Thrown when a payable entrypoint completes with unspent native value.
|
|
12
|
-
/// Override `settleValue` to implement refund or forwarding behavior instead.
|
|
13
11
|
error UnusedValue(uint remaining);
|
|
14
12
|
|
|
15
|
-
/// @notice
|
|
16
|
-
/// @return Budget
|
|
17
|
-
function
|
|
13
|
+
/// @notice Open a native-value budget from the current call's `msg.value`.
|
|
14
|
+
/// @return Budget initialized with the full `msg.value`.
|
|
15
|
+
function openValue() internal view returns (Budget memory) {
|
|
18
16
|
return Budget({remaining: msg.value});
|
|
19
17
|
}
|
|
20
18
|
|
|
@@ -24,36 +22,25 @@ abstract contract Payable {
|
|
|
24
22
|
/// @param resources Packed chain resources.
|
|
25
23
|
/// @return value Native value to forward in wei.
|
|
26
24
|
function useValue(Budget memory budget, uint resources) internal pure returns (uint128 value) {
|
|
27
|
-
|
|
25
|
+
value = uint128(resources);
|
|
26
|
+
Values.use(budget, value);
|
|
28
27
|
}
|
|
29
28
|
|
|
30
|
-
/// @notice
|
|
31
|
-
/// @dev EVM resources use the low 128 bits as native value/endowment.
|
|
32
|
-
/// @param budget Mutable parent budget to deduct from.
|
|
33
|
-
/// @param resources Packed chain resources.
|
|
34
|
-
/// @return A new budget with the EVM value lane remaining.
|
|
35
|
-
function allocateValue(Budget memory budget, uint resources) internal pure returns (Budget memory) {
|
|
36
|
-
return Values.allocate(budget, uint128(resources));
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/// @notice Drains the budget and settles any remaining native value.
|
|
40
|
-
/// @dev Calls the amount-based `settleValue` hook only when some value remains.
|
|
29
|
+
/// @notice Close a native-value budget and settle any drained value.
|
|
41
30
|
/// @param account Account identifier for the current invocation.
|
|
42
|
-
/// @param budget Mutable native-value budget
|
|
43
|
-
function
|
|
44
|
-
uint value = budget
|
|
31
|
+
/// @param budget Mutable native-value budget to close.
|
|
32
|
+
function closeValue(bytes32 account, Budget memory budget) internal {
|
|
33
|
+
uint value = Values.drain(budget);
|
|
45
34
|
if (value == 0) return;
|
|
46
|
-
budget.remaining = 0;
|
|
47
35
|
settleValue(account, value);
|
|
48
36
|
}
|
|
49
37
|
|
|
50
|
-
/// @notice
|
|
51
|
-
/// @dev Override
|
|
52
|
-
/// The default implementation rejects any leftover amount.
|
|
38
|
+
/// @notice Handle a drained native value amount.
|
|
39
|
+
/// @dev Override to refund or redirect unused value. The default rejects it.
|
|
53
40
|
/// @param account Account identifier for the current invocation.
|
|
54
|
-
/// @param
|
|
55
|
-
function settleValue(bytes32 account, uint
|
|
41
|
+
/// @param value Drained native value amount to settle, in wei.
|
|
42
|
+
function settleValue(bytes32 account, uint value) internal virtual {
|
|
56
43
|
account;
|
|
57
|
-
revert UnusedValue(
|
|
44
|
+
revert UnusedValue(value);
|
|
58
45
|
}
|
|
59
46
|
}
|
package/core/Pipeline.sol
CHANGED
|
@@ -32,6 +32,7 @@ abstract contract Pipeline is Payable {
|
|
|
32
32
|
|
|
33
33
|
/// @notice Execute a STEP block stream through the pipeline.
|
|
34
34
|
/// @dev Reverts with `UnexpectedState` if the final threaded state is non-empty.
|
|
35
|
+
/// Callers remain responsible for settling any unspent value in `budget`.
|
|
35
36
|
/// @param account Account identifier used for each dispatched step.
|
|
36
37
|
/// @param state Initial state block stream passed to the first step.
|
|
37
38
|
/// @param steps STEP block stream to execute.
|
|
@@ -50,7 +51,6 @@ abstract contract Pipeline is Payable {
|
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
if (state.length != 0) revert UnexpectedState();
|
|
53
|
-
settleValue(account, budget);
|
|
54
54
|
input.complete();
|
|
55
55
|
}
|
|
56
56
|
}
|
package/docs/Schema.md
CHANGED
|
@@ -58,7 +58,6 @@ Once a child block appears, no more fixed fields may follow.
|
|
|
58
58
|
```txt
|
|
59
59
|
#call { uint target, uint resources, #bytes as payload }
|
|
60
60
|
#context { bytes32 account, #bytes as state, #bytes as request }
|
|
61
|
-
#pipe { uint resources, #context { bytes32 account, #bytes as state, #bytes as steps } }
|
|
62
61
|
```
|
|
63
62
|
|
|
64
63
|
The tail is embedded directly as child block bytes. There is no wrapper around a
|
|
@@ -118,6 +117,18 @@ maybe #account { bytes32 account } as recipient
|
|
|
118
117
|
|
|
119
118
|
Aliases may be used on any block item, including child blocks and prime items.
|
|
120
119
|
|
|
120
|
+
A child block without an inline body may also be used as a schema reference:
|
|
121
|
+
|
|
122
|
+
```txt
|
|
123
|
+
#contextRecovery { uint port, bytes32 key, uint resources, #context as witness }
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Alias resolution is context-dependent. A consumer may resolve `#context` from the
|
|
127
|
+
standard `Schemas` table, from app-specific schemas, or from another active
|
|
128
|
+
schema context. Consumers should reject schemas with unresolved aliases. The
|
|
129
|
+
runtime encoding is still an embedded child block with the referenced key and
|
|
130
|
+
layout.
|
|
131
|
+
|
|
121
132
|
## Field Paths
|
|
122
133
|
|
|
123
134
|
Field names and aliases may use dotted paths for offchain projection. A dotted
|
|
@@ -271,7 +282,7 @@ Common protocol schemas live in `contracts/blocks/Schema.sol`:
|
|
|
271
282
|
#call { uint target, uint resources, #bytes as payload }
|
|
272
283
|
#step { uint target, uint resources, #bytes as request }
|
|
273
284
|
#context { bytes32 account, #bytes as state, #bytes as request }
|
|
274
|
-
#
|
|
285
|
+
#contextRecovery { uint port, bytes32 key, uint resources, #context as witness }
|
|
275
286
|
#auth { uint cid, uint deadline, #bytes as proof }
|
|
276
287
|
```
|
|
277
288
|
|
package/events/Commitment.sol
CHANGED
|
@@ -3,15 +3,15 @@ pragma solidity ^0.8.33;
|
|
|
3
3
|
|
|
4
4
|
import { EventEmitter } from "./Emitter.sol";
|
|
5
5
|
|
|
6
|
-
/// @notice Emitted when
|
|
6
|
+
/// @notice Emitted when a host-scoped commitment is updated.
|
|
7
7
|
abstract contract CommitmentEvent is EventEmitter {
|
|
8
|
-
string private constant ABI = "event Commitment(
|
|
8
|
+
string private constant ABI = "event Commitment(uint indexed host, bytes32 key, bytes32 digest, uint status)";
|
|
9
9
|
|
|
10
|
-
/// @param
|
|
10
|
+
/// @param host Host node ID that manages the commitment.
|
|
11
11
|
/// @param key Commitment lookup key.
|
|
12
12
|
/// @param digest Committed digest. Zero may be used when clearing without revealing the previous digest.
|
|
13
13
|
/// @param status Commitment status. Zero means cleared; nonzero means committed or application-defined.
|
|
14
|
-
event Commitment(
|
|
14
|
+
event Commitment(uint indexed host, bytes32 key, bytes32 digest, uint status);
|
|
15
15
|
|
|
16
16
|
constructor() {
|
|
17
17
|
emit EventAbi(ABI);
|
|
@@ -0,0 +1,20 @@
|
|
|
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 when a host records an outbound dispatch reference.
|
|
7
|
+
abstract contract DispatchEvent is EventEmitter {
|
|
8
|
+
string private constant ABI = "event Dispatch(uint indexed host, uint chain, uint resources, bytes32 digest, bytes32 ref)";
|
|
9
|
+
|
|
10
|
+
/// @param host Host node ID that owns the dispatch.
|
|
11
|
+
/// @param chain Destination chain/domain node ID.
|
|
12
|
+
/// @param resources Chain-adapter-specific resources assigned to the dispatch.
|
|
13
|
+
/// @param digest Digest of the dispatched payload or canonical envelope.
|
|
14
|
+
/// @param ref Dispatch correlation or recovery reference.
|
|
15
|
+
event Dispatch(uint indexed host, uint chain, uint resources, bytes32 digest, bytes32 ref);
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
emit EventAbi(ABI);
|
|
19
|
+
}
|
|
20
|
+
}
|
package/events/Port.sol
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
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 by hosts to advertise callable peer-facing ports.
|
|
7
|
+
abstract contract PortEvent is EventEmitter {
|
|
8
|
+
string private constant ABI =
|
|
9
|
+
"event Port(uint indexed host, uint id, bytes32 shape, string request, string response, bool funded)";
|
|
10
|
+
|
|
11
|
+
/// @param host Host node ID that exposes the port.
|
|
12
|
+
/// @param id Port node ID.
|
|
13
|
+
/// @param shape Block shape/version descriptor.
|
|
14
|
+
/// @param request Human-readable request schema.
|
|
15
|
+
/// @param response Human-readable response schema.
|
|
16
|
+
/// @param funded True if the port accepts native value.
|
|
17
|
+
event Port(uint indexed host, uint id, bytes32 shape, string request, string response, bool funded);
|
|
18
|
+
|
|
19
|
+
constructor() {
|
|
20
|
+
emit EventAbi(ABI);
|
|
21
|
+
}
|
|
22
|
+
}
|
package/guards/Base.sol
CHANGED
|
@@ -6,17 +6,6 @@ import {GuardEvent} from "../events/Guard.sol";
|
|
|
6
6
|
import {LabeledEvent} from "../events/Labeled.sol";
|
|
7
7
|
import {Nodes} from "../utils/Nodes.sol";
|
|
8
8
|
|
|
9
|
-
/// @notice ABI-encode a guard action call from a target guard ID and request block stream.
|
|
10
|
-
/// @dev Derives the function selector from `target` via `Nodes.guardSelector(target)`.
|
|
11
|
-
/// Reverts if `target` is not a valid guard ID.
|
|
12
|
-
/// @param target Destination guard action node ID embedding the target selector.
|
|
13
|
-
/// @param request Input block stream for the guard invocation.
|
|
14
|
-
/// @return ABI-encoded calldata for the guard action entry point.
|
|
15
|
-
function encodeGuardCall(uint target, bytes calldata request) pure returns (bytes memory) {
|
|
16
|
-
bytes4 selector = Nodes.guardSelector(target);
|
|
17
|
-
return abi.encodeWithSelector(selector, request);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
9
|
/// @title GuardBase
|
|
21
10
|
/// @notice Abstract base for guardian-only direct host actions.
|
|
22
11
|
/// Guard actions are non-payable direct calls with no command context, state, or response.
|
package/package.json
CHANGED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
|
+
pragma solidity ^0.8.33;
|
|
3
|
+
|
|
4
|
+
import { PortBase } from "./Base.sol";
|
|
5
|
+
import { AllowAssetsHook } from "../commands/admin/AllowAssets.sol";
|
|
6
|
+
import { Cursors, Cur, Schemas } from "../Cursors.sol";
|
|
7
|
+
|
|
8
|
+
using Cursors for Cur;
|
|
9
|
+
|
|
10
|
+
/// @title PortAllowAssets
|
|
11
|
+
/// @notice Port that permits a list of assets on behalf of a peer host.
|
|
12
|
+
/// Each ASSET block in the request calls `allowAsset`. Restricted to trusted peers.
|
|
13
|
+
abstract contract PortAllowAssets is PortBase, AllowAssetsHook {
|
|
14
|
+
uint internal immutable portAllowAssetsId = portId(this.portAllowAssets.selector);
|
|
15
|
+
|
|
16
|
+
constructor() {
|
|
17
|
+
emit Port(host, portAllowAssetsId, "1:0", Schemas.Asset, "", false);
|
|
18
|
+
emit Labeled(portAllowAssetsId, bytes32(0), "portAllowAssets");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/// @notice Execute the allow-assets peer call.
|
|
22
|
+
/// @param data ASSET block stream supplied by the trusted peer.
|
|
23
|
+
/// @return Empty response bytes.
|
|
24
|
+
function portAllowAssets(bytes calldata data) external onlyPeer returns (bytes memory) {
|
|
25
|
+
(Cur memory assets, , ) = Cursors.init(data, 1);
|
|
26
|
+
|
|
27
|
+
while (assets.i < assets.len) {
|
|
28
|
+
bytes32 asset = assets.unpackAsset();
|
|
29
|
+
allowAsset(asset);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
assets.complete();
|
|
33
|
+
return "";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
|
+
pragma solidity ^0.8.33;
|
|
3
|
+
|
|
4
|
+
import {PortBase} 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
|
+
/// @title PortAllowance
|
|
11
|
+
/// @notice Port that lets a trusted peer host request or refresh its own allowance.
|
|
12
|
+
/// Each AMOUNT block in the request is scoped to the peer host and passed to the
|
|
13
|
+
/// shared allowance hook as a host-scoped allowance. Restricted to trusted peers.
|
|
14
|
+
abstract contract PortAllowance is PortBase, AllowanceHook {
|
|
15
|
+
uint internal immutable portAllowanceId = portId(this.portAllowance.selector);
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
emit Port(host, portAllowanceId, "1:0", Schemas.Amount, "", false);
|
|
19
|
+
emit Labeled(portAllowanceId, bytes32(0), "portAllowance");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/// @notice Execute the allowance port call.
|
|
23
|
+
/// @param data AMOUNT block stream requested by the trusted peer.
|
|
24
|
+
/// @return Empty response bytes.
|
|
25
|
+
function portAllowance(bytes calldata data) external onlyPeer returns (bytes memory) {
|
|
26
|
+
(Cur memory amounts, , ) = Cursors.init(data, 1);
|
|
27
|
+
uint peer = caller();
|
|
28
|
+
|
|
29
|
+
while (amounts.i < amounts.len) {
|
|
30
|
+
(bytes32 asset, uint amount) = amounts.unpackAmount();
|
|
31
|
+
allowance(peer, asset, amount);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
amounts.complete();
|
|
35
|
+
return "";
|
|
36
|
+
}
|
|
37
|
+
}
|
package/ports/Base.sol
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
|
+
pragma solidity ^0.8.33;
|
|
3
|
+
|
|
4
|
+
import { NodeCalls } from "../core/Calls.sol";
|
|
5
|
+
import { PortEvent } from "../events/Port.sol";
|
|
6
|
+
import { LabeledEvent } from "../events/Labeled.sol";
|
|
7
|
+
import { Nodes } from "../utils/Nodes.sol";
|
|
8
|
+
|
|
9
|
+
/// @title PortBase
|
|
10
|
+
/// @notice Abstract base for peer-facing rootzero ports.
|
|
11
|
+
/// Ports handle inter-host operations between cooperating hosts.
|
|
12
|
+
/// Access is restricted to trusted peer callers via `onlyPeer`.
|
|
13
|
+
abstract contract PortBase is NodeCalls, PortEvent, LabeledEvent {
|
|
14
|
+
/// @dev Thrown when the commander attempts to call a port entrypoint directly.
|
|
15
|
+
error CommanderNotAllowed();
|
|
16
|
+
|
|
17
|
+
/// @dev Restrict execution to trusted callers, excluding the commander.
|
|
18
|
+
modifier onlyPeer() {
|
|
19
|
+
if (msg.sender == commander) revert CommanderNotAllowed();
|
|
20
|
+
enforceCaller(msg.sender);
|
|
21
|
+
_;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/// @notice Derive the deterministic node ID for a port selector on this contract.
|
|
25
|
+
/// @param selector Port entrypoint selector.
|
|
26
|
+
/// @return Port node ID.
|
|
27
|
+
function portId(bytes4 selector) internal view returns (uint) {
|
|
28
|
+
return Nodes.toPort(selector, address(this));
|
|
29
|
+
}
|
|
30
|
+
}
|
package/ports/Credit.sol
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
|
+
pragma solidity ^0.8.33;
|
|
3
|
+
|
|
4
|
+
import { PortBase } from "./Base.sol";
|
|
5
|
+
import { CreditAccountHook } from "../commands/Credit.sol";
|
|
6
|
+
import { Cursors, Cur, Forms } from "../Cursors.sol";
|
|
7
|
+
|
|
8
|
+
using Cursors for Cur;
|
|
9
|
+
|
|
10
|
+
/// @title PortCreditAccount
|
|
11
|
+
/// @notice Port that lets a trusted peer credit supplied accounts directly.
|
|
12
|
+
/// Each ACCOUNT_AMOUNT block calls `creditAccount` for its account.
|
|
13
|
+
abstract contract PortCreditAccount is PortBase, CreditAccountHook {
|
|
14
|
+
uint internal immutable portCreditAccountId = portId(this.portCreditAccount.selector);
|
|
15
|
+
|
|
16
|
+
constructor() {
|
|
17
|
+
emit Port(host, portCreditAccountId, "1:0", Forms.AccountAmount, "", false);
|
|
18
|
+
emit Labeled(portCreditAccountId, bytes32(0), "portCreditAccount");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/// @notice Execute the port-credit call.
|
|
22
|
+
/// @param data ACCOUNT_AMOUNT block stream supplied by the trusted peer.
|
|
23
|
+
/// @return Empty response bytes.
|
|
24
|
+
function portCreditAccount(bytes calldata data) external onlyPeer returns (bytes memory) {
|
|
25
|
+
(Cur memory amounts, , ) = Cursors.init(data, 1);
|
|
26
|
+
|
|
27
|
+
while (amounts.i < amounts.len) {
|
|
28
|
+
(bytes32 account, bytes32 asset, uint amount) = amounts.unpackAccountAmount();
|
|
29
|
+
creditAccount(account, asset, amount);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
amounts.complete();
|
|
33
|
+
return "";
|
|
34
|
+
}
|
|
35
|
+
}
|
package/ports/Debit.sol
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
|
+
pragma solidity ^0.8.33;
|
|
3
|
+
|
|
4
|
+
import { PortBase } from "./Base.sol";
|
|
5
|
+
import { DebitAccountHook } from "../commands/Debit.sol";
|
|
6
|
+
import { Cursors, Cur, Forms } from "../Cursors.sol";
|
|
7
|
+
|
|
8
|
+
using Cursors for Cur;
|
|
9
|
+
|
|
10
|
+
/// @title PortDebitAccount
|
|
11
|
+
/// @notice Port that lets a trusted peer debit supplied accounts directly.
|
|
12
|
+
/// Each ACCOUNT_AMOUNT block calls `debitAccount` for its account.
|
|
13
|
+
abstract contract PortDebitAccount is PortBase, DebitAccountHook {
|
|
14
|
+
uint internal immutable portDebitAccountId = portId(this.portDebitAccount.selector);
|
|
15
|
+
|
|
16
|
+
constructor() {
|
|
17
|
+
emit Port(host, portDebitAccountId, "1:0", Forms.AccountAmount, "", false);
|
|
18
|
+
emit Labeled(portDebitAccountId, bytes32(0), "portDebitAccount");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/// @notice Execute the port-debit call.
|
|
22
|
+
/// @param data ACCOUNT_AMOUNT block stream supplied by the trusted peer.
|
|
23
|
+
/// @return Empty response bytes.
|
|
24
|
+
function portDebitAccount(bytes calldata data) external onlyPeer returns (bytes memory) {
|
|
25
|
+
(Cur memory amounts, , ) = Cursors.init(data, 1);
|
|
26
|
+
|
|
27
|
+
while (amounts.i < amounts.len) {
|
|
28
|
+
(bytes32 account, bytes32 asset, uint amount) = amounts.unpackAccountAmount();
|
|
29
|
+
debitAccount(account, asset, amount);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
amounts.complete();
|
|
33
|
+
return "";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
|
+
pragma solidity ^0.8.33;
|
|
3
|
+
|
|
4
|
+
import {PortBase} from "./Base.sol";
|
|
5
|
+
import {DenyAssetsHook} from "../commands/admin/DenyAssets.sol";
|
|
6
|
+
import {Cursors, Cur, Schemas} from "../Cursors.sol";
|
|
7
|
+
|
|
8
|
+
using Cursors for Cur;
|
|
9
|
+
|
|
10
|
+
/// @title PortDenyAssets
|
|
11
|
+
/// @notice Port that blocks a list of assets on behalf of a peer host.
|
|
12
|
+
/// Each ASSET block in the request calls `denyAsset`. Restricted to trusted peers.
|
|
13
|
+
abstract contract PortDenyAssets is PortBase, DenyAssetsHook {
|
|
14
|
+
uint internal immutable portDenyAssetsId = portId(this.portDenyAssets.selector);
|
|
15
|
+
|
|
16
|
+
constructor() {
|
|
17
|
+
emit Port(host, portDenyAssetsId, "1:0", Schemas.Asset, "", false);
|
|
18
|
+
emit Labeled(portDenyAssetsId, bytes32(0), "portDenyAssets");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/// @notice Execute the deny-assets peer call.
|
|
22
|
+
/// @param data ASSET block stream supplied by the trusted peer.
|
|
23
|
+
/// @return Empty response bytes.
|
|
24
|
+
function portDenyAssets(bytes calldata data) external onlyPeer returns (bytes memory) {
|
|
25
|
+
(Cur memory assets, , ) = Cursors.init(data, 1);
|
|
26
|
+
|
|
27
|
+
while (assets.i < assets.len) {
|
|
28
|
+
bytes32 asset = assets.unpackAsset();
|
|
29
|
+
denyAsset(asset);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
assets.complete();
|
|
33
|
+
return "";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
@@ -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 { PortBase } from "./Base.sol";
|
|
5
5
|
import { Payable } from "../core/Payable.sol";
|
|
6
6
|
import { Cursors, Cur, Schemas } from "../Cursors.sol";
|
|
7
7
|
import { DispatchPayableHook } from "../commands/Relay.sol";
|
|
@@ -9,28 +9,24 @@ import { Budget } from "../utils/Value.sol";
|
|
|
9
9
|
|
|
10
10
|
using Cursors for Cur;
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
/// @title PeerDispatchPayable
|
|
17
|
-
/// @notice Peer endpoint that forwards DISPATCH blocks to a host-defined dispatch hook.
|
|
18
|
-
abstract contract PeerDispatchPayable is PeerBase, Payable, DispatchPayableHook, IPeerDispatchPayable {
|
|
19
|
-
uint internal immutable peerDispatchPayableId = peerId(this.peerDispatchPayable.selector);
|
|
12
|
+
/// @title PortDispatchPayable
|
|
13
|
+
/// @notice Port endpoint that forwards DISPATCH blocks to a host-defined dispatch hook.
|
|
14
|
+
abstract contract PortDispatchPayable is PortBase, Payable, DispatchPayableHook {
|
|
15
|
+
uint internal immutable portDispatchPayableId = portId(this.portDispatchPayable.selector);
|
|
20
16
|
|
|
21
17
|
constructor() {
|
|
22
|
-
emit
|
|
23
|
-
emit Labeled(
|
|
18
|
+
emit Port(host, portDispatchPayableId, "1:0", Schemas.Dispatch, "", true);
|
|
19
|
+
emit Labeled(portDispatchPayableId, bytes32(0), "portDispatchPayable");
|
|
24
20
|
}
|
|
25
21
|
|
|
26
22
|
/// @notice Forward peer-supplied dispatches to the host-defined dispatch hook.
|
|
27
23
|
/// @dev Dispatch hooks receive the shared top-level source-chain value
|
|
28
24
|
/// budget. Any `msg.value` not spent by the hook remains on this host.
|
|
29
|
-
/// @param
|
|
25
|
+
/// @param data DISPATCH block stream supplied by the trusted peer.
|
|
30
26
|
/// @return output Empty response bytes.
|
|
31
|
-
function
|
|
32
|
-
(Cur memory input, , ) = Cursors.init(
|
|
33
|
-
Budget memory budget =
|
|
27
|
+
function portDispatchPayable(bytes calldata data) external payable onlyPeer returns (bytes memory output) {
|
|
28
|
+
(Cur memory input, , ) = Cursors.init(data, 1);
|
|
29
|
+
Budget memory budget = openValue();
|
|
34
30
|
|
|
35
31
|
while (input.i < input.len) {
|
|
36
32
|
(uint chain, uint resources, bytes calldata payload) = input.unpackDispatch();
|