@rootzero/contracts 1.28.0 → 1.30.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 +77 -0
- package/Codec.sol +2 -1
- package/Commands.sol +1 -1
- package/Core.sol +1 -1
- package/Endpoints.sol +7 -7
- package/README.md +97 -43
- package/Utils.sol +2 -1
- package/codec/Blocks.sol +41 -67
- package/codec/Decoders.sol +12 -20
- package/codec/Descriptors.sol +3 -14
- package/codec/Keys.sol +1 -3
- package/codec/Schema.sol +1 -3
- package/codec/Specs.sol +2 -4
- package/codec/Writers.sol +8 -9
- package/commands/Base.sol +3 -2
- package/commands/Bootstrap.sol +3 -1
- package/commands/Cashout.sol +24 -21
- package/commands/Credit.sol +6 -5
- package/commands/Debit.sol +7 -6
- package/commands/Relay.sol +14 -15
- package/commands/Repay.sol +6 -5
- package/commands/Settle.sol +6 -5
- package/commands/admin/Execute.sol +2 -2
- package/core/Access.sol +14 -12
- package/core/Calls.sol +90 -102
- package/core/Host.sol +13 -11
- package/core/Pipeline.sol +179 -19
- package/core/Settlement.sol +1 -1
- package/execution/Execution.sol +25 -37
- package/package.json +1 -1
- package/ports/Allowance.sol +7 -17
- package/ports/Base.sol +4 -1
- package/ports/Dispatch.sol +1 -1
- package/ports/Pipe.sol +1 -1
- package/queries/Base.sol +1 -1
- package/utils/Accounts.sol +4 -4
- package/utils/Assets.sol +4 -5
- package/utils/Cursors.sol +24 -1
- package/utils/Flags.sol +18 -0
- package/utils/Layout.sol +3 -3
- package/utils/Nodes.sol +36 -31
- package/utils/Utils.sol +5 -5
package/core/Access.sol
CHANGED
|
@@ -6,7 +6,6 @@ import {GuardianEvent} from "../events/Guardian.sol";
|
|
|
6
6
|
import {Runtime} from "./Runtime.sol";
|
|
7
7
|
import {Accounts} from "../utils/Accounts.sol";
|
|
8
8
|
import {Nodes} from "../utils/Nodes.sol";
|
|
9
|
-
import {addrOr} from "../utils/Utils.sol";
|
|
10
9
|
|
|
11
10
|
/// @dev Thrown when a caller, account, or node lacks required access.
|
|
12
11
|
error AccessDenied();
|
|
@@ -37,20 +36,23 @@ abstract contract TrustAccess {
|
|
|
37
36
|
/// @title CommanderAccess
|
|
38
37
|
/// @notice Minimal commander-based access control shared by host access policies.
|
|
39
38
|
abstract contract CommanderAccess is Runtime {
|
|
40
|
-
/// @dev
|
|
41
|
-
|
|
42
|
-
address internal immutable commander;
|
|
39
|
+
/// @dev Commander host ID. Defaults to this contract's host ID when self-managed.
|
|
40
|
+
uint internal immutable commander;
|
|
43
41
|
|
|
44
|
-
/// @
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
/// @dev Native commander address resolved from `commander` at construction.
|
|
43
|
+
address internal immutable commanderAddr;
|
|
44
|
+
|
|
45
|
+
/// @param cmdr Local host ID of the commander, or zero to make this contract self-managed.
|
|
46
|
+
constructor(uint cmdr) {
|
|
47
|
+
commander = cmdr == 0 ? host : cmdr;
|
|
48
|
+
commanderAddr = cmdr == 0 ? address(this) : Nodes.hostAddr(cmdr);
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
/// @notice Assert that `caller` is the commander and return it.
|
|
50
52
|
/// @param caller Address to validate.
|
|
51
53
|
/// @return The same `caller` value if it is the commander.
|
|
52
54
|
function enforceCommander(address caller) internal view returns (address) {
|
|
53
|
-
if (caller == address(0) || caller !=
|
|
55
|
+
if (caller == address(0) || caller != commanderAddr) {
|
|
54
56
|
revert AccessDenied();
|
|
55
57
|
}
|
|
56
58
|
return caller;
|
|
@@ -61,11 +63,11 @@ abstract contract CommanderAccess is Runtime {
|
|
|
61
63
|
/// @title AdminAccess
|
|
62
64
|
/// @notice Commander access with the admin account derived from the commander.
|
|
63
65
|
abstract contract AdminAccess is CommanderAccess {
|
|
64
|
-
/// @dev Admin account ID derived from the commander address at construction time.
|
|
66
|
+
/// @dev Admin account ID derived from the resolved commander address at construction time.
|
|
65
67
|
bytes32 internal immutable admin;
|
|
66
68
|
|
|
67
69
|
constructor() {
|
|
68
|
-
admin = Accounts.toAdmin(
|
|
70
|
+
admin = Accounts.toAdmin(commanderAddr);
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
/// @notice Assert that `account` is the admin account and `caller` is the commander.
|
|
@@ -99,7 +101,7 @@ abstract contract NodeAccess is AdminAccess, TrustAccess, NodeEvent {
|
|
|
99
101
|
/// whose host ID has been explicitly authorized.
|
|
100
102
|
/// @param caller Address to check.
|
|
101
103
|
function isTrustedCaller(address caller) internal view returns (bool) {
|
|
102
|
-
return caller ==
|
|
104
|
+
return caller == commanderAddr || caller == address(this) || nodes[Nodes.toHost(caller)];
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
/// @notice Assert that `node` is in the trusted set and return it.
|
|
@@ -112,7 +114,7 @@ abstract contract NodeAccess is AdminAccess, TrustAccess, NodeEvent {
|
|
|
112
114
|
|
|
113
115
|
/// @notice Assert that `caller` is a trusted peer other than the commander.
|
|
114
116
|
function enforcePeer(address caller) internal view returns (address) {
|
|
115
|
-
if (caller ==
|
|
117
|
+
if (caller == commanderAddr) revert CommanderNotAllowed();
|
|
116
118
|
if (caller == address(0) || !isTrustedCaller(caller)) {
|
|
117
119
|
revert AccessDenied();
|
|
118
120
|
}
|
package/core/Calls.sol
CHANGED
|
@@ -10,122 +10,110 @@ import {Nodes} from "../utils/Nodes.sol";
|
|
|
10
10
|
/// @param err Revert data returned by the failed call.
|
|
11
11
|
error FailedCall(address addr, bytes4 selector, bytes err);
|
|
12
12
|
|
|
13
|
-
/// @notice
|
|
14
|
-
/// @
|
|
15
|
-
///
|
|
16
|
-
///
|
|
17
|
-
|
|
13
|
+
/// @notice Try a raw low-level call to another node and return whether it succeeded.
|
|
14
|
+
/// @param node Node ID of the callee.
|
|
15
|
+
/// @param value Native value to forward in wei.
|
|
16
|
+
/// @param data Complete encoded calldata to send.
|
|
17
|
+
/// @return success True if the low-level call succeeded.
|
|
18
|
+
function tryRawCall(uint node, uint value, bytes memory data) returns (bool success) {
|
|
19
|
+
address addr = Nodes.addr(node);
|
|
20
|
+
(success, ) = payable(addr).call{value: value}(data);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// @notice Try a raw low-level call using a separate selector and ABI-encoded arguments.
|
|
24
|
+
/// @dev `args` must not include the selector. The byte-array length is temporarily
|
|
25
|
+
/// used as scratch space so the call does not allocate or copy `selector || args`.
|
|
26
|
+
function tryRawCall(
|
|
18
27
|
bytes4 selector,
|
|
19
|
-
address
|
|
28
|
+
address addr,
|
|
20
29
|
uint value,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
bytes calldata input
|
|
24
|
-
) returns (bytes memory output, uint credit) {
|
|
30
|
+
bytes memory args
|
|
31
|
+
) returns (bool success) {
|
|
25
32
|
assembly ("memory-safe") {
|
|
26
|
-
let
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
// ABI envelope for command(bytes).
|
|
31
|
-
mstore(scratch, selector)
|
|
32
|
-
mstore(add(scratch, 0x04), 0x20)
|
|
33
|
-
mstore(add(scratch, 0x24), ctxlen)
|
|
34
|
-
|
|
35
|
-
// CONTEXT(account, BYTES(state), BYTES(input)).
|
|
36
|
-
let ctx := add(scratch, 0x44)
|
|
37
|
-
// 0xc5769e23 = bytes4(keccak256("#context"))
|
|
38
|
-
mstore(ctx, or(shl(224, 0xc5769e23), shl(192, sub(ctxlen, 8))))
|
|
39
|
-
// `ctxlen` is dead after the header and can carry the complete call length.
|
|
40
|
-
ctxlen := add(68, and(add(ctxlen, 0x1f), not(0x1f)))
|
|
41
|
-
mstore(add(ctx, 0x08), account)
|
|
42
|
-
let stateblk := add(ctx, 0x28)
|
|
43
|
-
// 0x6911b332 = bytes4(keccak256("#bytes"))
|
|
44
|
-
mstore(stateblk, or(shl(224, 0x6911b332), shl(192, statelen)))
|
|
45
|
-
mcopy(add(stateblk, 0x08), add(state, 0x20), statelen)
|
|
46
|
-
let inputblk := add(add(stateblk, 0x08), statelen)
|
|
47
|
-
let inputlen := input.length
|
|
48
|
-
mstore(inputblk, or(shl(224, 0x6911b332), shl(192, inputlen)))
|
|
49
|
-
calldatacopy(add(inputblk, 0x08), input.offset, inputlen)
|
|
50
|
-
|
|
51
|
-
// ABI word alignment also covers the input header's full-word write. Advance
|
|
52
|
-
// the free memory pointer once, after the return-data size is also known.
|
|
53
|
-
let inputend := and(add(add(scratch, ctxlen), 0x1f), not(0x1f))
|
|
54
|
-
let success := call(gas(), target, value, scratch, ctxlen, 0, 0)
|
|
55
|
-
let retlen := returndatasize()
|
|
56
|
-
if iszero(success) {
|
|
57
|
-
// FailedCall(target, selector, returndata)
|
|
58
|
-
// 0x20577b07 = FailedCall(address,bytes4,bytes)
|
|
59
|
-
mstore(scratch, shl(224, 0x20577b07))
|
|
60
|
-
mstore(add(scratch, 0x04), target)
|
|
61
|
-
mstore(add(scratch, 0x24), selector)
|
|
62
|
-
mstore(add(scratch, 0x44), 0x60)
|
|
63
|
-
mstore(add(scratch, 0x64), retlen)
|
|
64
|
-
mstore(add(add(scratch, 0x84), retlen), 0)
|
|
65
|
-
returndatacopy(add(scratch, 0x84), 0, retlen)
|
|
66
|
-
revert(scratch, add(0x84, and(add(retlen, 0x1f), not(0x1f))))
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if lt(retlen, 0x60) { revert(0, 0) }
|
|
70
|
-
returndatacopy(scratch, 0, retlen)
|
|
71
|
-
|
|
72
|
-
// Strictly validate the exact ABI layout for (bytes, uint). The returned
|
|
73
|
-
// byte array can then point directly into the copied returndata.
|
|
74
|
-
if iszero(eq(mload(scratch), 0x40)) { revert(0, 0) }
|
|
75
|
-
let len1 := mload(add(scratch, 0x40))
|
|
76
|
-
if gt(len1, sub(retlen, 0x60)) { revert(0, 0) }
|
|
77
|
-
let pad1 := and(add(len1, 0x1f), not(0x1f))
|
|
78
|
-
if iszero(eq(retlen, add(0x60, pad1))) { revert(0, 0) }
|
|
79
|
-
output := add(scratch, 0x40)
|
|
80
|
-
credit := mload(add(scratch, 0x20))
|
|
81
|
-
|
|
82
|
-
let retend := and(add(add(scratch, retlen), 0x1f), not(0x1f))
|
|
83
|
-
if gt(retend, inputend) { inputend := retend }
|
|
84
|
-
mstore(0x40, inputend)
|
|
33
|
+
let len := mload(args)
|
|
34
|
+
mstore(args, or(and(len, not(0xffffffff)), shr(224, selector)))
|
|
35
|
+
success := call(gas(), addr, value, add(args, 0x1c), add(len, 0x04), 0, 0)
|
|
36
|
+
mstore(args, len)
|
|
85
37
|
}
|
|
86
38
|
}
|
|
87
39
|
|
|
88
|
-
/// @
|
|
89
|
-
/// @
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
40
|
+
/// @notice Make a raw low-level call to another node and revert when it fails.
|
|
41
|
+
/// @param node Node ID of the callee.
|
|
42
|
+
/// @param value Native value to forward in wei.
|
|
43
|
+
/// @param data Complete encoded calldata to send.
|
|
44
|
+
/// @return out Return data from the successful call.
|
|
45
|
+
function rawCall(uint node, uint value, bytes memory data) returns (bytes memory out) {
|
|
46
|
+
bool success;
|
|
47
|
+
address addr = Nodes.addr(node);
|
|
48
|
+
(success, out) = payable(addr).call{value: value}(data);
|
|
49
|
+
if (!success) revert FailedCall(addr, bytes4(data), out);
|
|
50
|
+
}
|
|
100
51
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
52
|
+
/// @notice Make a raw low-level call using a separate selector and ABI-encoded arguments.
|
|
53
|
+
/// @dev `args` must not include the selector. The byte-array length is temporarily
|
|
54
|
+
/// used as scratch space so the call does not allocate or copy `selector || args`.
|
|
55
|
+
function rawCall(
|
|
56
|
+
bytes4 selector,
|
|
57
|
+
address addr,
|
|
58
|
+
uint value,
|
|
59
|
+
bytes memory args
|
|
60
|
+
) returns (bytes memory out) {
|
|
61
|
+
bool success;
|
|
62
|
+
assembly ("memory-safe") {
|
|
63
|
+
let len := mload(args)
|
|
64
|
+
mstore(args, or(and(len, not(0xffffffff)), shr(224, selector)))
|
|
65
|
+
success := call(gas(), addr, value, add(args, 0x1c), add(len, 0x04), 0, 0)
|
|
66
|
+
mstore(args, len)
|
|
67
|
+
|
|
68
|
+
let size := returndatasize()
|
|
69
|
+
out := mload(0x40)
|
|
70
|
+
mstore(out, size)
|
|
71
|
+
returndatacopy(add(out, 0x20), 0, size)
|
|
72
|
+
mstore(add(add(out, 0x20), size), 0)
|
|
73
|
+
mstore(0x40, and(add(add(add(out, 0x20), size), 0x1f), not(0x1f)))
|
|
111
74
|
}
|
|
75
|
+
if (!success) revert FailedCall(addr, selector, out);
|
|
76
|
+
}
|
|
112
77
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
78
|
+
/// @notice Make a raw low-level read-only query to another node and revert when it fails.
|
|
79
|
+
/// @param node Node ID of the callee.
|
|
80
|
+
/// @param data Complete encoded calldata to send.
|
|
81
|
+
/// @return out Return data from the successful query.
|
|
82
|
+
function rawQuery(uint node, bytes memory data) view returns (bytes memory out) {
|
|
83
|
+
bool success;
|
|
84
|
+
address addr = Nodes.addr(node);
|
|
85
|
+
(success, out) = addr.staticcall(data);
|
|
86
|
+
if (!success) revert FailedCall(addr, bytes4(data), out);
|
|
87
|
+
}
|
|
123
88
|
|
|
89
|
+
/// @notice Make a raw read-only query using a separate selector and ABI-encoded arguments.
|
|
90
|
+
/// @dev `args` must not include the selector. The byte-array length is temporarily
|
|
91
|
+
/// used as scratch space so the call does not allocate or copy `selector || args`.
|
|
92
|
+
function rawQuery(
|
|
93
|
+
bytes4 selector,
|
|
94
|
+
address addr,
|
|
95
|
+
bytes memory args
|
|
96
|
+
) view returns (bytes memory out) {
|
|
97
|
+
bool success;
|
|
98
|
+
assembly ("memory-safe") {
|
|
99
|
+
let len := mload(args)
|
|
100
|
+
mstore(args, or(and(len, not(0xffffffff)), shr(224, selector)))
|
|
101
|
+
success := staticcall(gas(), addr, add(args, 0x1c), add(len, 0x04), 0, 0)
|
|
102
|
+
mstore(args, len)
|
|
103
|
+
|
|
104
|
+
let size := returndatasize()
|
|
105
|
+
out := mload(0x40)
|
|
106
|
+
mstore(out, size)
|
|
107
|
+
returndatacopy(add(out, 0x20), 0, size)
|
|
108
|
+
mstore(add(add(out, 0x20), size), 0)
|
|
109
|
+
mstore(0x40, and(add(add(add(out, 0x20), size), 0x1f), not(0x1f)))
|
|
110
|
+
}
|
|
111
|
+
if (!success) revert FailedCall(addr, selector, out);
|
|
124
112
|
}
|
|
125
113
|
|
|
126
114
|
/// @title NodeCalls
|
|
127
115
|
/// @notice Trusted low-level inter-node calls backed by a host-provided node policy.
|
|
128
|
-
abstract contract NodeCalls is
|
|
116
|
+
abstract contract NodeCalls is TrustAccess {
|
|
129
117
|
/// @notice Try a trusted low-level call to another node and return whether it succeeded.
|
|
130
118
|
/// @param node Node ID of the callee.
|
|
131
119
|
/// @param value Native value to forward in wei.
|
package/core/Host.sol
CHANGED
|
@@ -28,11 +28,13 @@ interface IHostIntroduction {
|
|
|
28
28
|
/// Calls a deployed commander during construction without adding an inbound
|
|
29
29
|
/// introduction endpoint to the inheriting host.
|
|
30
30
|
abstract contract HostIntroduction is Runtime {
|
|
31
|
-
/// @param cmdr
|
|
31
|
+
/// @param cmdr Local host ID to introduce this host to, or zero for no introduction.
|
|
32
32
|
/// @dev Deployment reverts if a contract commander does not accept `introduce(uint,uint)`.
|
|
33
|
-
constructor(
|
|
34
|
-
if (cmdr ==
|
|
35
|
-
|
|
33
|
+
constructor(uint cmdr) {
|
|
34
|
+
if (cmdr == 0 || cmdr == host) return;
|
|
35
|
+
address target = Nodes.hostAddr(cmdr);
|
|
36
|
+
if (target.code.length == 0) return;
|
|
37
|
+
IHostIntroduction(target).introduce(host, block.number);
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
/// @notice Introduce this host to the contract address embedded in a local EVM node ID.
|
|
@@ -53,9 +55,9 @@ abstract contract CommandHost is CommanderAccess, CallerAccess, HostIntroduction
|
|
|
53
55
|
/// @dev Thrown when a commander-only host is deployed without an external commander.
|
|
54
56
|
error InvalidCommander();
|
|
55
57
|
|
|
56
|
-
/// @param cmdr Nonzero
|
|
57
|
-
constructor(
|
|
58
|
-
if (cmdr ==
|
|
58
|
+
/// @param cmdr Nonzero local host ID allowed to invoke hosted commands.
|
|
59
|
+
constructor(uint cmdr) CommanderAccess(cmdr) HostIntroduction(cmdr) {
|
|
60
|
+
if (cmdr == 0) revert InvalidCommander();
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
function enforceCaller(address caller) internal view virtual override returns (address) {
|
|
@@ -82,10 +84,10 @@ abstract contract Guardians is Appoint, Dismiss, Revoke {
|
|
|
82
84
|
/// optionally introduces itself to a commander host at deployment.
|
|
83
85
|
/// Accepts native ETH payments via the `receive` function.
|
|
84
86
|
abstract contract Host is Admins, Guardians, HostIntroduction, IntroductionEvent, IHostIntroduction {
|
|
85
|
-
/// @param cmdr Commander
|
|
86
|
-
/// If
|
|
87
|
-
/// on it during construction.
|
|
88
|
-
constructor(
|
|
87
|
+
/// @param cmdr Commander host ID; used by the composed access capabilities.
|
|
88
|
+
/// If the encoded native target is a deployed contract, the host
|
|
89
|
+
/// calls `introduce` on it during construction.
|
|
90
|
+
constructor(uint cmdr) CommanderAccess(cmdr) HostIntroduction(cmdr) {}
|
|
89
91
|
|
|
90
92
|
/// @notice Assert that `caller` may invoke commands on a peer-aware host.
|
|
91
93
|
function enforceCaller(address caller) internal view virtual override returns (address) {
|
package/core/Pipeline.sol
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
pragma solidity ^0.8.33;
|
|
3
3
|
|
|
4
4
|
import {Blocks} from "../codec/Blocks.sol";
|
|
5
|
+
import {Keys} from "../codec/Keys.sol";
|
|
5
6
|
import {TrustAccess} from "./Access.sol";
|
|
6
|
-
import {rawCommandCall} from "./Calls.sol";
|
|
7
|
-
import {Cursors} from "../utils/Cursors.sol";
|
|
8
7
|
import {InsufficientValue, OutOfBounds, UnexpectedState} from "../utils/Errors.sol";
|
|
9
|
-
import {
|
|
8
|
+
import {Nodes} from "../utils/Nodes.sol";
|
|
9
|
+
import {Flags} from "../utils/Flags.sol";
|
|
10
10
|
|
|
11
11
|
/// @notice Hook implemented by hosts that execute encoded step streams.
|
|
12
12
|
abstract contract PipeHook {
|
|
@@ -21,31 +21,188 @@ abstract contract PipeHook {
|
|
|
21
21
|
|
|
22
22
|
/// @notice Hook implemented by pipeline hosts that execute host-local commands.
|
|
23
23
|
abstract contract ExecuteHook {
|
|
24
|
-
/// @notice
|
|
25
|
-
/// @dev Implementations
|
|
24
|
+
/// @notice Try to execute one command whose node ID targets the current host.
|
|
25
|
+
/// @dev Implementations returning `handled = true` are responsible for
|
|
26
|
+
/// authorizing the command. Return false without side effects to delegate to
|
|
27
|
+
/// the trusted normal external entrypoint. Handoff commands must be delegated
|
|
28
|
+
/// because this hook receives ordinary input without the continuation that
|
|
29
|
+
/// Pipeline adds to the RELAY envelope. Implementations may revert instead.
|
|
26
30
|
function execute(
|
|
27
31
|
uint cmd,
|
|
28
32
|
bytes32 account,
|
|
29
33
|
bytes memory state,
|
|
30
34
|
bytes calldata input,
|
|
31
35
|
uint value
|
|
32
|
-
) internal virtual returns (bytes memory output, uint credit);
|
|
36
|
+
) internal virtual returns (bool handled, bytes memory output, uint credit);
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
/// @title Pipeline
|
|
36
40
|
/// @notice Core pipeline functionality shared by higher-level surfaces.
|
|
41
|
+
/// @dev This gas-sensitive implementation intentionally inlines the command ID
|
|
42
|
+
/// layout and the CONTEXT, BYTES, and RELAY block encodings. Changes to command
|
|
43
|
+
/// selector, target, or flag placement, or to those block layouts, must update
|
|
44
|
+
/// the corresponding assembly and packed-cursor logic here.
|
|
37
45
|
abstract contract Pipeline is TrustAccess, PipeHook, ExecuteHook {
|
|
38
|
-
|
|
46
|
+
/// @dev Private pipeline cursor layout:
|
|
47
|
+
/// bits 0-31 current STEP offset, 32-63 stream end,
|
|
48
|
+
/// 64-95 command-input offset, 96-127 command-input length.
|
|
49
|
+
function unpackBytes(uint abs) private pure returns (uint input, uint end) {
|
|
50
|
+
uint body;
|
|
51
|
+
(body, end) = Blocks.expectKey(abs, Keys.Bytes);
|
|
52
|
+
// `expectKey` constructs `end` as `body + uint32(length)`.
|
|
53
|
+
unchecked {
|
|
54
|
+
input = uint32(body) | ((end - body) << 32);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function takeStep(uint cursor) private pure returns (uint cmd, uint value, uint updated) {
|
|
59
|
+
uint abs = uint32(cursor);
|
|
60
|
+
uint limit;
|
|
61
|
+
(abs, limit) = Blocks.expectKey(abs, Keys.Step);
|
|
62
|
+
assembly ("memory-safe") {
|
|
63
|
+
cmd := calldataload(abs)
|
|
64
|
+
value := calldataload(add(abs, 0x20))
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
(uint input, uint end) = unpackBytes(abs + 64);
|
|
68
|
+
if (end != limit) revert Blocks.InvalidBlock();
|
|
69
|
+
if (end > uint32(cursor >> 32)) revert OutOfBounds();
|
|
70
|
+
|
|
71
|
+
updated = uint32(end) | (uint64(cursor) & (uint(type(uint32).max) << 32)) | (input << 64);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function rawInput(uint cursor) private pure returns (bytes calldata input) {
|
|
75
|
+
assembly ("memory-safe") {
|
|
76
|
+
input.offset := and(shr(64, cursor), 0xffffffff)
|
|
77
|
+
input.length := and(shr(96, cursor), 0xffffffff)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/// @dev Execute the prepared command call and strictly decode `(bytes, uint)`.
|
|
82
|
+
function invokeCommand(
|
|
39
83
|
uint cmd,
|
|
84
|
+
uint value,
|
|
40
85
|
bytes32 account,
|
|
41
86
|
bytes memory state,
|
|
42
|
-
|
|
43
|
-
uint value
|
|
87
|
+
uint input
|
|
44
88
|
) private returns (bytes memory output, uint credit) {
|
|
45
|
-
(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
89
|
+
assembly ("memory-safe") {
|
|
90
|
+
let scratch := mload(0x40)
|
|
91
|
+
let ctxlen
|
|
92
|
+
switch iszero(shr(64, input))
|
|
93
|
+
case 1 {
|
|
94
|
+
let statelen := mload(state)
|
|
95
|
+
let inputlen := and(shr(32, input), 0xffffffff)
|
|
96
|
+
ctxlen := add(56, add(statelen, inputlen))
|
|
97
|
+
|
|
98
|
+
mstore(scratch, shl(224, and(shr(160, cmd), 0xffffffff)))
|
|
99
|
+
mstore(add(scratch, 0x04), 0x20)
|
|
100
|
+
mstore(add(scratch, 0x24), ctxlen)
|
|
101
|
+
|
|
102
|
+
let p := add(scratch, 0x44)
|
|
103
|
+
mstore(p, or(shl(224, 0xc5769e23), shl(192, sub(ctxlen, 8))))
|
|
104
|
+
mstore(add(p, 0x08), account)
|
|
105
|
+
p := add(p, 0x28)
|
|
106
|
+
mstore(p, or(shl(224, 0x6911b332), shl(192, statelen)))
|
|
107
|
+
mcopy(add(p, 0x08), add(state, 0x20), statelen)
|
|
108
|
+
p := add(add(p, 0x08), statelen)
|
|
109
|
+
mstore(p, or(shl(224, 0x6911b332), shl(192, inputlen)))
|
|
110
|
+
calldatacopy(add(p, 0x08), and(input, 0xffffffff), inputlen)
|
|
111
|
+
}
|
|
112
|
+
default {
|
|
113
|
+
let statelen := mload(state)
|
|
114
|
+
let inputlen := and(shr(96, input), 0xffffffff)
|
|
115
|
+
let stepslen := sub(and(shr(32, input), 0xffffffff), and(input, 0xffffffff))
|
|
116
|
+
let relaylen := add(16, add(inputlen, stepslen))
|
|
117
|
+
ctxlen := add(64, add(statelen, relaylen))
|
|
118
|
+
|
|
119
|
+
mstore(scratch, shl(224, and(shr(160, cmd), 0xffffffff)))
|
|
120
|
+
mstore(add(scratch, 0x04), 0x20)
|
|
121
|
+
mstore(add(scratch, 0x24), ctxlen)
|
|
122
|
+
|
|
123
|
+
let p := add(scratch, 0x44)
|
|
124
|
+
mstore(p, or(shl(224, 0xc5769e23), shl(192, sub(ctxlen, 8))))
|
|
125
|
+
mstore(add(p, 0x08), account)
|
|
126
|
+
p := add(p, 0x28)
|
|
127
|
+
mstore(p, or(shl(224, 0x6911b332), shl(192, statelen)))
|
|
128
|
+
mcopy(add(p, 0x08), add(state, 0x20), statelen)
|
|
129
|
+
|
|
130
|
+
p := add(add(p, 0x08), statelen)
|
|
131
|
+
mstore(p, or(shl(224, 0x6911b332), shl(192, add(8, relaylen))))
|
|
132
|
+
p := add(p, 0x08)
|
|
133
|
+
mstore(p, or(shl(224, 0xc34cc52a), shl(192, relaylen)))
|
|
134
|
+
p := add(p, 0x08)
|
|
135
|
+
mstore(p, or(shl(224, 0x6911b332), shl(192, inputlen)))
|
|
136
|
+
calldatacopy(add(p, 0x08), and(shr(64, input), 0xffffffff), inputlen)
|
|
137
|
+
p := add(add(p, 0x08), inputlen)
|
|
138
|
+
mstore(p, or(shl(224, 0x6911b332), shl(192, stepslen)))
|
|
139
|
+
calldatacopy(add(p, 0x08), and(input, 0xffffffff), stepslen)
|
|
140
|
+
}
|
|
141
|
+
ctxlen := add(68, and(add(ctxlen, 0x1f), not(0x1f)))
|
|
142
|
+
let inputend := and(add(add(scratch, ctxlen), 0x1f), not(0x1f))
|
|
143
|
+
let success := call(
|
|
144
|
+
gas(),
|
|
145
|
+
and(cmd, 0xffffffffffffffffffffffffffffffffffffffff),
|
|
146
|
+
value,
|
|
147
|
+
scratch,
|
|
148
|
+
ctxlen,
|
|
149
|
+
0,
|
|
150
|
+
0
|
|
151
|
+
)
|
|
152
|
+
let retlen := returndatasize()
|
|
153
|
+
if iszero(success) {
|
|
154
|
+
mstore(scratch, shl(224, 0x20577b07))
|
|
155
|
+
mstore(add(scratch, 0x04), and(cmd, 0xffffffffffffffffffffffffffffffffffffffff))
|
|
156
|
+
mstore(add(scratch, 0x24), shl(224, and(shr(160, cmd), 0xffffffff)))
|
|
157
|
+
mstore(add(scratch, 0x44), 0x60)
|
|
158
|
+
mstore(add(scratch, 0x64), retlen)
|
|
159
|
+
mstore(add(add(scratch, 0x84), retlen), 0)
|
|
160
|
+
returndatacopy(add(scratch, 0x84), 0, retlen)
|
|
161
|
+
revert(scratch, add(0x84, and(add(retlen, 0x1f), not(0x1f))))
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if lt(retlen, 0x60) {
|
|
165
|
+
revert(0, 0)
|
|
166
|
+
}
|
|
167
|
+
returndatacopy(scratch, 0, retlen)
|
|
168
|
+
if iszero(eq(mload(scratch), 0x40)) {
|
|
169
|
+
revert(0, 0)
|
|
170
|
+
}
|
|
171
|
+
let len1 := mload(add(scratch, 0x40))
|
|
172
|
+
if gt(len1, sub(retlen, 0x60)) {
|
|
173
|
+
revert(0, 0)
|
|
174
|
+
}
|
|
175
|
+
let pad1 := and(add(len1, 0x1f), not(0x1f))
|
|
176
|
+
if iszero(eq(retlen, add(0x60, pad1))) {
|
|
177
|
+
revert(0, 0)
|
|
178
|
+
}
|
|
179
|
+
output := add(scratch, 0x40)
|
|
180
|
+
credit := mload(add(scratch, 0x20))
|
|
181
|
+
|
|
182
|
+
let retend := and(add(add(scratch, retlen), 0x1f), not(0x1f))
|
|
183
|
+
if gt(retend, inputend) {
|
|
184
|
+
inputend := retend
|
|
185
|
+
}
|
|
186
|
+
mstore(0x40, inputend)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function run(
|
|
191
|
+
uint cmd,
|
|
192
|
+
bytes32 account,
|
|
193
|
+
bytes memory state,
|
|
194
|
+
uint value,
|
|
195
|
+
uint cursor
|
|
196
|
+
) private returns (bytes memory output, uint credit, uint updated) {
|
|
197
|
+
if (address(uint160(cmd)) == address(this)) {
|
|
198
|
+
bool handled;
|
|
199
|
+
(handled, output, credit) = execute(cmd, account, state, rawInput(cursor), value);
|
|
200
|
+
if (handled) return (output, credit, cursor);
|
|
201
|
+
}
|
|
202
|
+
ensureTrusted(Nodes.command(cmd));
|
|
203
|
+
bool handoff = uint8(cmd >> 224) & Flags.Handoff != 0;
|
|
204
|
+
(output, credit) = invokeCommand(cmd, value, account, state, handoff ? cursor : cursor >> 64);
|
|
205
|
+
updated = handoff ? (cursor & ~uint(type(uint32).max)) | uint32(cursor >> 32) : cursor;
|
|
49
206
|
}
|
|
50
207
|
|
|
51
208
|
/// @notice Execute a STEP block stream through the pipeline.
|
|
@@ -62,18 +219,21 @@ abstract contract Pipeline is TrustAccess, PipeHook, ExecuteHook {
|
|
|
62
219
|
bytes calldata steps,
|
|
63
220
|
uint budget
|
|
64
221
|
) internal virtual override returns (uint remaining) {
|
|
65
|
-
|
|
222
|
+
uint cursor;
|
|
223
|
+
assembly ("memory-safe") {
|
|
224
|
+
cursor := or(steps.offset, shl(32, add(steps.offset, steps.length)))
|
|
225
|
+
}
|
|
66
226
|
|
|
67
|
-
while (
|
|
68
|
-
|
|
69
|
-
|
|
227
|
+
while (uint32(cursor) < uint32(cursor >> 32)) {
|
|
228
|
+
uint cmd;
|
|
229
|
+
uint value;
|
|
230
|
+
(cmd, value, cursor) = takeStep(cursor);
|
|
70
231
|
if (value > budget) revert InsufficientValue();
|
|
71
232
|
unchecked {
|
|
72
233
|
budget -= value;
|
|
73
234
|
}
|
|
74
|
-
(state, value) = run(cmd, account, state,
|
|
235
|
+
(state, value, cursor) = run(cmd, account, state, value, cursor);
|
|
75
236
|
budget += value;
|
|
76
|
-
abs = next;
|
|
77
237
|
}
|
|
78
238
|
|
|
79
239
|
if (state.length != 0) revert UnexpectedState();
|
package/core/Settlement.sol
CHANGED
|
@@ -44,7 +44,7 @@ abstract contract SettleHook {
|
|
|
44
44
|
|
|
45
45
|
/// @title Settlement
|
|
46
46
|
/// @notice Default account-hook implementation for transaction posting and position settlement.
|
|
47
|
-
abstract contract Settlement is PostHook,
|
|
47
|
+
abstract contract Settlement is PostHook, DebitAccountHook, CreditAccountHook, SettleHook, RepayHook {
|
|
48
48
|
/// @notice Post one transaction by debiting its source and crediting its destination.
|
|
49
49
|
/// Returns without calling either hook when `amount` is zero and skips either
|
|
50
50
|
/// operation when the corresponding account is zero.
|