@rootzero/contracts 0.9.3 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Commands.sol +3 -2
- package/Core.sol +3 -0
- package/Cursors.sol +1 -1
- package/README.md +18 -24
- package/blocks/Cursors.sol +294 -339
- package/blocks/Keys.sol +38 -57
- package/blocks/Schema.sol +55 -208
- package/blocks/Writers.sol +345 -135
- package/commands/Base.sol +6 -48
- package/commands/Burn.sol +2 -2
- package/commands/Credit.sol +3 -3
- package/commands/Debit.sol +3 -2
- package/commands/Deposit.sol +11 -8
- package/commands/Provision.sol +11 -8
- package/commands/Transfer.sol +2 -2
- package/commands/Withdraw.sol +3 -3
- package/commands/admin/AllowAssets.sol +1 -1
- package/commands/admin/Allowance.sol +1 -1
- package/commands/admin/Authorize.sol +1 -1
- package/commands/admin/DenyAssets.sol +1 -1
- package/commands/admin/Execute.sol +7 -6
- package/commands/admin/Unauthorize.sol +1 -1
- package/core/Access.sol +11 -0
- package/core/Context.sol +3 -4
- package/core/Payable.sol +57 -0
- package/core/Pipeline.sol +55 -0
- package/docs/Schema.md +194 -0
- package/events/Command.sol +1 -2
- package/package.json +2 -2
- package/peer/AllowAssets.sol +1 -1
- package/peer/Allowance.sol +1 -1
- package/peer/BalancePull.sol +1 -1
- package/peer/DenyAssets.sol +1 -1
- package/peer/Pipe.sol +38 -0
- package/peer/Settle.sol +1 -1
- package/queries/Assets.sol +4 -3
- package/queries/Balances.sol +2 -1
- package/queries/Positions.sol +12 -12
- package/utils/Value.sol +8 -14
- package/commands/Pipe.sol +0 -67
- package/docs/GETTING_STARTED.md +0 -294
package/commands/Base.sol
CHANGED
|
@@ -2,11 +2,9 @@
|
|
|
2
2
|
pragma solidity ^0.8.33;
|
|
3
3
|
|
|
4
4
|
import {NodeCalls} from "../core/Calls.sol";
|
|
5
|
-
import {Cur} from "../Cursors.sol";
|
|
6
5
|
import {CommandEvent} from "../events/Command.sol";
|
|
7
6
|
import {Keys} from "../blocks/Keys.sol";
|
|
8
7
|
import {Ids, Selectors} from "../utils/Ids.sol";
|
|
9
|
-
import {Budget, Values} from "../utils/Value.sol";
|
|
10
8
|
|
|
11
9
|
/// @notice Execution context passed to every command invocation.
|
|
12
10
|
struct CommandContext {
|
|
@@ -25,22 +23,18 @@ struct CommandContext {
|
|
|
25
23
|
abstract contract CommandBase is NodeCalls, CommandEvent {
|
|
26
24
|
/// @dev Thrown when `onlyActive` finds that `deadline` has already passed.
|
|
27
25
|
error Expired();
|
|
28
|
-
/// @dev Thrown when the raw active account word in calldata does not match the decoded context account.
|
|
29
|
-
error ActiveAccountMismatch();
|
|
30
26
|
/// @dev Thrown when `onlyAdmin` finds that `account` is not the admin account.
|
|
31
27
|
error NotAdmin();
|
|
32
28
|
|
|
33
|
-
/// @dev Restrict execution to
|
|
34
|
-
modifier
|
|
35
|
-
if (
|
|
36
|
-
|
|
29
|
+
/// @dev Restrict execution to the commander using the host's admin account.
|
|
30
|
+
modifier onlyAdmin(bytes32 account) {
|
|
31
|
+
if (account != adminAccount) revert NotAdmin();
|
|
32
|
+
enforceCommander(msg.sender);
|
|
37
33
|
_;
|
|
38
34
|
}
|
|
39
35
|
|
|
40
|
-
/// @dev Restrict execution to trusted callers
|
|
41
|
-
modifier
|
|
42
|
-
if (activeAccount() != account) revert ActiveAccountMismatch();
|
|
43
|
-
if (account != adminAccount) revert NotAdmin();
|
|
36
|
+
/// @dev Restrict execution to trusted callers.
|
|
37
|
+
modifier onlyCommand() {
|
|
44
38
|
enforceCaller(msg.sender);
|
|
45
39
|
_;
|
|
46
40
|
}
|
|
@@ -66,40 +60,4 @@ abstract contract CommandBase is NodeCalls, CommandEvent {
|
|
|
66
60
|
function commandId(string memory name) internal view returns (uint) {
|
|
67
61
|
return Ids.toCommand(Selectors.command(name), address(this));
|
|
68
62
|
}
|
|
69
|
-
|
|
70
|
-
/// @notice Return the active command account directly from the fixed tuple head in calldata.
|
|
71
|
-
/// @dev Command entrypoints use the ABI shape `name((bytes32,bytes,bytes))`, so the tuple head
|
|
72
|
-
/// starts at byte 36 of `msg.data`, with the account field at bytes [36:68).
|
|
73
|
-
function activeAccount() internal pure returns (bytes32 account) {
|
|
74
|
-
account = bytes32(msg.data[36:68]);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/// @title CommandPayable
|
|
79
|
-
/// @notice Abstract base for commands that accept native value (`msg.value`).
|
|
80
|
-
/// Provides a shared settlement hook for any unspent value remaining in the
|
|
81
|
-
/// command's mutable budget after execution completes.
|
|
82
|
-
abstract contract CommandPayable is CommandBase {
|
|
83
|
-
/// @dev Thrown when a payable command completes with unspent native value.
|
|
84
|
-
/// Override `settleValue` to implement refund or forwarding behavior instead.
|
|
85
|
-
error UnusedValue(uint remaining);
|
|
86
|
-
|
|
87
|
-
/// @notice Drains the command budget and settles any remaining native value.
|
|
88
|
-
/// @dev Calls the amount-based `settleValue` hook only when some value remains.
|
|
89
|
-
/// @param account Caller's account identifier for the current invocation.
|
|
90
|
-
/// @param budget Mutable native-value budget used during command execution.
|
|
91
|
-
function settleValue(bytes32 account, Budget memory budget) internal {
|
|
92
|
-
uint remaining = Values.drain(budget);
|
|
93
|
-
if (remaining != 0) settleValue(account, remaining);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/// @notice Handles leftover native value after a payable command has finished.
|
|
97
|
-
/// @dev Override this hook to refund or redirect unused value for a command.
|
|
98
|
-
/// The default implementation rejects any leftover amount.
|
|
99
|
-
/// @param account Caller's account identifier for the current invocation.
|
|
100
|
-
/// @param remaining Unspent native value left in the budget, in wei.
|
|
101
|
-
function settleValue(bytes32 account, uint remaining) internal virtual {
|
|
102
|
-
account;
|
|
103
|
-
revert UnusedValue(remaining);
|
|
104
|
-
}
|
|
105
63
|
}
|
package/commands/Burn.sol
CHANGED
|
@@ -28,7 +28,7 @@ abstract contract Burn is CommandBase, BurnHook {
|
|
|
28
28
|
emit Command(host, burnId, NAME, "0:1:0", "", Keys.Balance, Keys.Empty, false);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
function burn(CommandContext calldata c) external onlyCommand
|
|
31
|
+
function burn(CommandContext calldata c) external onlyCommand returns (bytes memory) {
|
|
32
32
|
(Cur memory state, ) = cursor(c.state, 1);
|
|
33
33
|
|
|
34
34
|
while (state.i < state.bound) {
|
|
@@ -36,7 +36,7 @@ abstract contract Burn is CommandBase, BurnHook {
|
|
|
36
36
|
burn(c.account, asset, meta, amount);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
state.
|
|
39
|
+
state.close();
|
|
40
40
|
return "";
|
|
41
41
|
}
|
|
42
42
|
}
|
package/commands/Credit.sol
CHANGED
|
@@ -22,7 +22,7 @@ abstract contract CreditAccountHook {
|
|
|
22
22
|
/// An optional ACCOUNT block in the request overrides the default `c.account` destination.
|
|
23
23
|
abstract contract CreditAccount is CommandBase, CreditAccountHook {
|
|
24
24
|
string private constant NAME = "creditAccount";
|
|
25
|
-
string private constant REQUEST = string.concat(Schemas.
|
|
25
|
+
string private constant REQUEST = string.concat(Schemas.Unit, ", maybe ", Schemas.Account);
|
|
26
26
|
|
|
27
27
|
uint internal immutable creditAccountId = commandId(NAME);
|
|
28
28
|
|
|
@@ -32,7 +32,7 @@ abstract contract CreditAccount is CommandBase, CreditAccountHook {
|
|
|
32
32
|
|
|
33
33
|
function creditAccount(
|
|
34
34
|
CommandContext calldata c
|
|
35
|
-
) external onlyCommand
|
|
35
|
+
) external onlyCommand returns (bytes memory) {
|
|
36
36
|
(Cur memory state, ) = cursor(c.state, 1);
|
|
37
37
|
bytes32 to = Cursors.resolveAccount(c.request, c.account);
|
|
38
38
|
|
|
@@ -41,7 +41,7 @@ abstract contract CreditAccount is CommandBase, CreditAccountHook {
|
|
|
41
41
|
creditAccount(to, asset, meta, amount);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
state.
|
|
44
|
+
state.close();
|
|
45
45
|
return "";
|
|
46
46
|
}
|
|
47
47
|
}
|
package/commands/Debit.sol
CHANGED
|
@@ -43,12 +43,13 @@ abstract contract DebitAccount is CommandBase, DebitAccountHook {
|
|
|
43
43
|
writer.appendBalance(asset, meta, amount);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
input.close();
|
|
47
|
+
return writer.finish();
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
function debitAccount(
|
|
50
51
|
CommandContext calldata c
|
|
51
|
-
) external onlyCommand
|
|
52
|
+
) external onlyCommand returns (bytes memory) {
|
|
52
53
|
return debitAccount(c.account, c.request);
|
|
53
54
|
}
|
|
54
55
|
}
|
package/commands/Deposit.sol
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
2
|
pragma solidity ^0.8.33;
|
|
3
3
|
|
|
4
|
-
import { CommandContext, CommandBase,
|
|
4
|
+
import { CommandContext, CommandBase, Keys } from "./Base.sol";
|
|
5
|
+
import { Payable } from "../core/Payable.sol";
|
|
5
6
|
import { Cursors, Cur, Schemas, Writer, Writers } from "../Cursors.sol";
|
|
6
|
-
import { Budget
|
|
7
|
+
import { Budget } from "../utils/Value.sol";
|
|
7
8
|
|
|
8
9
|
using Cursors for Cur;
|
|
9
10
|
using Writers for Writer;
|
|
@@ -46,7 +47,7 @@ abstract contract Deposit is CommandBase, DepositHook {
|
|
|
46
47
|
|
|
47
48
|
function deposit(
|
|
48
49
|
CommandContext calldata c
|
|
49
|
-
) external onlyCommand
|
|
50
|
+
) external onlyCommand returns (bytes memory) {
|
|
50
51
|
(Cur memory request, uint groups) = cursor(c.request, 1);
|
|
51
52
|
Writer memory writer = Writers.allocBalances(groups);
|
|
52
53
|
|
|
@@ -56,14 +57,15 @@ abstract contract Deposit is CommandBase, DepositHook {
|
|
|
56
57
|
writer.appendBalance(asset, meta, amount);
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
|
|
60
|
+
request.close();
|
|
61
|
+
return writer.finish();
|
|
60
62
|
}
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
/// @title DepositPayable
|
|
64
66
|
/// @notice Command that receives externally sourced assets and records them as BALANCE state.
|
|
65
67
|
/// Use `depositPayable` when the hook needs tracked access to `msg.value` via a mutable budget.
|
|
66
|
-
abstract contract DepositPayable is
|
|
68
|
+
abstract contract DepositPayable is CommandBase, Payable, DepositPayableHook {
|
|
67
69
|
string private constant NAME = "depositPayable";
|
|
68
70
|
|
|
69
71
|
uint internal immutable depositPayableId = commandId(NAME);
|
|
@@ -74,10 +76,10 @@ abstract contract DepositPayable is CommandPayable, DepositPayableHook {
|
|
|
74
76
|
|
|
75
77
|
function depositPayable(
|
|
76
78
|
CommandContext calldata c
|
|
77
|
-
) external payable onlyCommand
|
|
79
|
+
) external payable onlyCommand returns (bytes memory) {
|
|
78
80
|
(Cur memory request, uint groups) = cursor(c.request, 1);
|
|
79
81
|
Writer memory writer = Writers.allocBalances(groups);
|
|
80
|
-
Budget memory budget =
|
|
82
|
+
Budget memory budget = valueBudget();
|
|
81
83
|
|
|
82
84
|
while (request.i < request.bound) {
|
|
83
85
|
(bytes32 asset, bytes32 meta, uint amount) = request.unpackAmount();
|
|
@@ -86,7 +88,8 @@ abstract contract DepositPayable is CommandPayable, DepositPayableHook {
|
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
settleValue(c.account, budget);
|
|
89
|
-
|
|
91
|
+
request.close();
|
|
92
|
+
return writer.finish();
|
|
90
93
|
}
|
|
91
94
|
}
|
|
92
95
|
|
package/commands/Provision.sol
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
2
|
pragma solidity ^0.8.33;
|
|
3
3
|
|
|
4
|
-
import {CommandContext, CommandBase,
|
|
4
|
+
import {CommandContext, CommandBase, Keys} from "./Base.sol";
|
|
5
|
+
import {Payable} from "../core/Payable.sol";
|
|
5
6
|
import {HostAmount, Cursors, Cur, Schemas, Writer, Writers} from "../Cursors.sol";
|
|
6
|
-
import {Budget
|
|
7
|
+
import {Budget} from "../utils/Value.sol";
|
|
7
8
|
using Cursors for Cur;
|
|
8
9
|
using Writers for Writer;
|
|
9
10
|
|
|
@@ -40,7 +41,7 @@ abstract contract Provision is CommandBase, ProvisionHook {
|
|
|
40
41
|
emit Command(host, provisionId, NAME, "1:0:1", Schemas.Allocation, Keys.Empty, Keys.Custody, false);
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
function provision(CommandContext calldata c) external onlyCommand
|
|
44
|
+
function provision(CommandContext calldata c) external onlyCommand returns (bytes memory) {
|
|
44
45
|
(Cur memory request, uint groups) = cursor(c.request, 1);
|
|
45
46
|
Writer memory writer = Writers.allocCustodies(groups);
|
|
46
47
|
|
|
@@ -50,7 +51,8 @@ abstract contract Provision is CommandBase, ProvisionHook {
|
|
|
50
51
|
writer.appendCustody(allocation);
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
|
|
54
|
+
request.close();
|
|
55
|
+
return writer.finish();
|
|
54
56
|
}
|
|
55
57
|
}
|
|
56
58
|
|
|
@@ -58,7 +60,7 @@ abstract contract Provision is CommandBase, ProvisionHook {
|
|
|
58
60
|
/// @notice Command that provisions assets to peer hosts from ALLOCATION request blocks.
|
|
59
61
|
/// Each request block supplies the target host plus an asset amount; the output is a CUSTODY state stream.
|
|
60
62
|
/// The hook receives a mutable native-value budget drawn from `msg.value`.
|
|
61
|
-
abstract contract ProvisionPayable is
|
|
63
|
+
abstract contract ProvisionPayable is CommandBase, Payable, ProvisionPayableHook {
|
|
62
64
|
string private constant NAME = "provisionPayable";
|
|
63
65
|
|
|
64
66
|
uint internal immutable provisionPayableId = commandId(NAME);
|
|
@@ -69,10 +71,10 @@ abstract contract ProvisionPayable is CommandPayable, ProvisionPayableHook {
|
|
|
69
71
|
|
|
70
72
|
function provisionPayable(
|
|
71
73
|
CommandContext calldata c
|
|
72
|
-
) external payable onlyCommand
|
|
74
|
+
) external payable onlyCommand returns (bytes memory) {
|
|
73
75
|
(Cur memory request, uint groups) = cursor(c.request, 1);
|
|
74
76
|
Writer memory writer = Writers.allocCustodies(groups);
|
|
75
|
-
Budget memory budget =
|
|
77
|
+
Budget memory budget = valueBudget();
|
|
76
78
|
|
|
77
79
|
while (request.i < request.bound) {
|
|
78
80
|
HostAmount memory allocation = request.unpackAllocationValue();
|
|
@@ -81,7 +83,8 @@ abstract contract ProvisionPayable is CommandPayable, ProvisionPayableHook {
|
|
|
81
83
|
}
|
|
82
84
|
|
|
83
85
|
settleValue(c.account, budget);
|
|
84
|
-
|
|
86
|
+
request.close();
|
|
87
|
+
return writer.finish();
|
|
85
88
|
}
|
|
86
89
|
}
|
|
87
90
|
|
package/commands/Transfer.sol
CHANGED
|
@@ -42,13 +42,13 @@ abstract contract Transfer is CommandBase, TransferHook {
|
|
|
42
42
|
transfer(value);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
input.
|
|
45
|
+
input.close();
|
|
46
46
|
return "";
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
function transfer(
|
|
50
50
|
CommandContext calldata c
|
|
51
|
-
) external onlyCommand
|
|
51
|
+
) external onlyCommand returns (bytes memory) {
|
|
52
52
|
return transfer(c.account, c.request);
|
|
53
53
|
}
|
|
54
54
|
}
|
package/commands/Withdraw.sol
CHANGED
|
@@ -21,7 +21,7 @@ abstract contract WithdrawHook {
|
|
|
21
21
|
/// For internal balance credits, use `creditAccount` instead.
|
|
22
22
|
abstract contract Withdraw is CommandBase, WithdrawHook {
|
|
23
23
|
string private constant NAME = "withdraw";
|
|
24
|
-
string private constant REQUEST = string.concat(Schemas.
|
|
24
|
+
string private constant REQUEST = string.concat(Schemas.Unit, ", maybe ", Schemas.Account);
|
|
25
25
|
|
|
26
26
|
uint internal immutable withdrawId = commandId(NAME);
|
|
27
27
|
|
|
@@ -31,7 +31,7 @@ abstract contract Withdraw is CommandBase, WithdrawHook {
|
|
|
31
31
|
|
|
32
32
|
function withdraw(
|
|
33
33
|
CommandContext calldata c
|
|
34
|
-
) external onlyCommand
|
|
34
|
+
) external onlyCommand returns (bytes memory) {
|
|
35
35
|
(Cur memory state, ) = cursor(c.state, 1);
|
|
36
36
|
bytes32 to = Cursors.resolveAccount(c.request, c.account);
|
|
37
37
|
|
|
@@ -40,7 +40,7 @@ abstract contract Withdraw is CommandBase, WithdrawHook {
|
|
|
40
40
|
withdraw(to, asset, meta, amount);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
state.
|
|
43
|
+
state.close();
|
|
44
44
|
return "";
|
|
45
45
|
}
|
|
46
46
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
2
|
pragma solidity ^0.8.33;
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {CommandBase, CommandContext, Keys} from "../Base.sol";
|
|
5
|
+
import {Payable} from "../../core/Payable.sol";
|
|
5
6
|
import {Cursors, Cur, Schemas} from "../../Cursors.sol";
|
|
6
7
|
import {AdminEvent} from "../../events/Admin.sol";
|
|
7
|
-
import {Budget
|
|
8
|
+
import {Budget} from "../../utils/Value.sol";
|
|
8
9
|
import {Ids} from "../../utils/Ids.sol";
|
|
9
10
|
|
|
10
11
|
using Cursors for Cur;
|
|
@@ -13,7 +14,7 @@ using Cursors for Cur;
|
|
|
13
14
|
/// @notice Admin command that forwards raw calldata to one or more target nodes.
|
|
14
15
|
/// Each CALL block specifies a target node ID, native value, and raw calldata payload.
|
|
15
16
|
/// Only callable by the admin account.
|
|
16
|
-
abstract contract ExecutePayable is
|
|
17
|
+
abstract contract ExecutePayable is CommandBase, Payable, AdminEvent {
|
|
17
18
|
string private constant NAME = "executePayable";
|
|
18
19
|
|
|
19
20
|
uint internal immutable executePayableId = commandId(NAME);
|
|
@@ -24,15 +25,15 @@ abstract contract ExecutePayable is CommandPayable, AdminEvent {
|
|
|
24
25
|
|
|
25
26
|
function executePayable(CommandContext calldata c) external payable onlyAdmin(c.account) returns (bytes memory) {
|
|
26
27
|
(Cur memory request, ) = cursor(c.request, 1);
|
|
27
|
-
Budget memory budget =
|
|
28
|
+
Budget memory budget = valueBudget();
|
|
28
29
|
|
|
29
30
|
while (request.i < request.bound) {
|
|
30
31
|
(uint target, uint value, bytes calldata data) = request.unpackCall();
|
|
31
32
|
address addr = Ids.nodeAddr(target);
|
|
32
|
-
callAddr(addr,
|
|
33
|
+
callAddr(addr, useValue(budget, value), data);
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
request.
|
|
36
|
+
request.close();
|
|
36
37
|
settleValue(c.account, budget);
|
|
37
38
|
return "";
|
|
38
39
|
}
|
package/core/Access.sol
CHANGED
|
@@ -78,4 +78,15 @@ abstract contract AccessControl is RootZeroContext, AccessEvent {
|
|
|
78
78
|
}
|
|
79
79
|
return caller;
|
|
80
80
|
}
|
|
81
|
+
|
|
82
|
+
/// @notice Assert that `caller` is the commander and return it.
|
|
83
|
+
/// Used by admin modifiers to keep governance authority separate from peer trust.
|
|
84
|
+
/// @param caller Address to validate.
|
|
85
|
+
/// @return The same `caller` value if it is the commander.
|
|
86
|
+
function enforceCommander(address caller) internal view returns (address) {
|
|
87
|
+
if (caller == address(0) || caller != commander) {
|
|
88
|
+
revert UnauthorizedCaller(caller);
|
|
89
|
+
}
|
|
90
|
+
return caller;
|
|
91
|
+
}
|
|
81
92
|
}
|
package/core/Context.sol
CHANGED
|
@@ -29,8 +29,7 @@ abstract contract RootZeroContext {
|
|
|
29
29
|
/// @return cur Cursor with `bound` set to the end of the first run.
|
|
30
30
|
/// @return groups Number of block groups in the run (`prime block count / group`).
|
|
31
31
|
function cursor(bytes calldata source, uint group) internal pure returns (Cur memory cur, uint groups) {
|
|
32
|
-
|
|
33
|
-
(, , groups) = cur.primeRun(group);
|
|
32
|
+
return Cursors.init(source, group);
|
|
34
33
|
}
|
|
35
34
|
|
|
36
35
|
/// @notice Open a cursor, prime it, and assert that its group count matches `expectedGroups`.
|
|
@@ -41,8 +40,8 @@ abstract contract RootZeroContext {
|
|
|
41
40
|
/// @param expectedGroups Required number of groups in the first run.
|
|
42
41
|
/// @return cur Cursor with `bound` set to the end of the first run.
|
|
43
42
|
function cursor(bytes calldata source, uint group, uint expectedGroups) internal pure returns (Cur memory cur) {
|
|
44
|
-
|
|
45
|
-
(,
|
|
43
|
+
uint groups;
|
|
44
|
+
(cur, groups) = Cursors.init(source, group);
|
|
46
45
|
if (groups != expectedGroups) revert Cursors.BadRatio();
|
|
47
46
|
}
|
|
48
47
|
}
|
package/core/Payable.sol
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
|
+
pragma solidity ^0.8.33;
|
|
3
|
+
|
|
4
|
+
import {Budget, Values} from "../utils/Value.sol";
|
|
5
|
+
|
|
6
|
+
/// @title Payable
|
|
7
|
+
/// @notice Abstract mixin for entrypoints that accept native value (`msg.value`).
|
|
8
|
+
/// Provides a shared settlement hook for any unspent value remaining in the
|
|
9
|
+
/// mutable budget after execution completes.
|
|
10
|
+
abstract contract Payable {
|
|
11
|
+
/// @dev Thrown when a payable entrypoint completes with unspent native value.
|
|
12
|
+
/// Override `settleValue` to implement refund or forwarding behavior instead.
|
|
13
|
+
error UnusedValue(uint remaining);
|
|
14
|
+
|
|
15
|
+
/// @notice Create a native-value budget from the current call's `msg.value`.
|
|
16
|
+
/// @return Budget initialised with the full `msg.value`.
|
|
17
|
+
function valueBudget() internal view returns (Budget memory) {
|
|
18
|
+
return Budget({remaining: msg.value});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/// @notice Deduct `amount` from the budget and return it.
|
|
22
|
+
/// @param budget Mutable budget to deduct from.
|
|
23
|
+
/// @param amount Native value to spend.
|
|
24
|
+
/// @return The same `amount`, ready to forward to a callee.
|
|
25
|
+
function useValue(Budget memory budget, uint amount) internal pure returns (uint) {
|
|
26
|
+
return Values.use(budget, amount);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// @notice Deduct `amount` from the budget and return it as a new sub-budget.
|
|
30
|
+
/// @param budget Mutable parent budget to deduct from.
|
|
31
|
+
/// @param amount Native value to assign to the sub-budget.
|
|
32
|
+
/// @return A new budget with `amount` remaining.
|
|
33
|
+
function allocateValue(Budget memory budget, uint amount) internal pure returns (Budget memory) {
|
|
34
|
+
return Values.allocate(budget, amount);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/// @notice Drains the budget and settles any remaining native value.
|
|
38
|
+
/// @dev Calls the amount-based `settleValue` hook only when some value remains.
|
|
39
|
+
/// @param account Account identifier for the current invocation.
|
|
40
|
+
/// @param budget Mutable native-value budget used during execution.
|
|
41
|
+
function settleValue(bytes32 account, Budget memory budget) internal {
|
|
42
|
+
uint value = budget.remaining;
|
|
43
|
+
if (value == 0) return;
|
|
44
|
+
budget.remaining = 0;
|
|
45
|
+
settleValue(account, value);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// @notice Handles leftover native value after payable execution has finished.
|
|
49
|
+
/// @dev Override this hook to refund or redirect unused value.
|
|
50
|
+
/// The default implementation rejects any leftover amount.
|
|
51
|
+
/// @param account Account identifier for the current invocation.
|
|
52
|
+
/// @param remaining Unspent native value left in the budget, in wei.
|
|
53
|
+
function settleValue(bytes32 account, uint remaining) internal virtual {
|
|
54
|
+
account;
|
|
55
|
+
revert UnusedValue(remaining);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
|
+
pragma solidity ^0.8.33;
|
|
3
|
+
|
|
4
|
+
import {Cursors, Cur} from "../Cursors.sol";
|
|
5
|
+
import {Payable} from "./Payable.sol";
|
|
6
|
+
import {Budget} from "../utils/Value.sol";
|
|
7
|
+
|
|
8
|
+
using Cursors for Cur;
|
|
9
|
+
|
|
10
|
+
/// @title Pipeline
|
|
11
|
+
/// @notice Core pipeline functionality shared by higher-level surfaces.
|
|
12
|
+
abstract contract Pipeline is Payable {
|
|
13
|
+
error UnexpectedState();
|
|
14
|
+
|
|
15
|
+
/// @notice Override to dispatch one piped step.
|
|
16
|
+
/// Called once per STEP block. The returned bytes become the state passed to
|
|
17
|
+
/// the next step, and the final returned state must be empty.
|
|
18
|
+
/// @param target Node ID to invoke or handle.
|
|
19
|
+
/// @param account Account identifier for the piped context.
|
|
20
|
+
/// @param state Current threaded state block stream.
|
|
21
|
+
/// @param request Step request block stream.
|
|
22
|
+
/// @param value Native value assigned to this step.
|
|
23
|
+
/// @return Updated state block stream for the next step.
|
|
24
|
+
function dispatch(
|
|
25
|
+
uint target,
|
|
26
|
+
bytes32 account,
|
|
27
|
+
bytes memory state,
|
|
28
|
+
bytes calldata request,
|
|
29
|
+
uint value
|
|
30
|
+
) internal virtual returns (bytes memory);
|
|
31
|
+
|
|
32
|
+
/// @notice Execute a STEP block stream through the pipeline.
|
|
33
|
+
/// @dev Reverts with `UnexpectedState` if the final threaded state is non-empty.
|
|
34
|
+
/// @param account Account identifier used for each dispatched step.
|
|
35
|
+
/// @param state Initial state block stream passed to the first step.
|
|
36
|
+
/// @param steps STEP block stream to execute.
|
|
37
|
+
/// @param budget Mutable native-value budget shared across all steps.
|
|
38
|
+
function pipe(
|
|
39
|
+
bytes32 account,
|
|
40
|
+
bytes memory state,
|
|
41
|
+
bytes calldata steps,
|
|
42
|
+
Budget memory budget
|
|
43
|
+
) internal {
|
|
44
|
+
(Cur memory input, ) = Cursors.init(steps, 1);
|
|
45
|
+
|
|
46
|
+
while (input.i < input.bound) {
|
|
47
|
+
(uint target, uint value, bytes calldata request) = input.unpackStep();
|
|
48
|
+
state = dispatch(target, account, state, request, useValue(budget, value));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (state.length != 0) revert UnexpectedState();
|
|
52
|
+
settleValue(account, budget);
|
|
53
|
+
input.close();
|
|
54
|
+
}
|
|
55
|
+
}
|