@rootzero/contracts 1.11.0 → 1.13.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/core/Payable.sol CHANGED
@@ -2,15 +2,16 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import {Budget, Values} from "../utils/Value.sol";
5
+ import {Cursors} from "../Cursors.sol";
6
+ import {ReceivedEvent} from "../events/Received.sol";
7
+ import {Actions} from "../utils/Actions.sol";
8
+ import {NativeAsset} from "./Runtime.sol";
5
9
  import {max128} from "../utils/Utils.sol";
6
10
 
7
11
  /// @title Payable
8
12
  /// @notice Abstract mixin for entrypoints that accept native value (`msg.value`).
9
13
  /// Provides shared helpers for mutable native-value budgets.
10
- abstract contract Payable {
11
- /// @dev Thrown when a payable entrypoint completes with unspent native value.
12
- error UnusedValue(uint remaining);
13
-
14
+ abstract contract Payable is NativeAsset, ReceivedEvent {
14
15
  /// @notice Open a native-value budget from the current call's `msg.value`.
15
16
  /// @return Budget initialized with the full `msg.value`.
16
17
  function openValue() internal view returns (Budget memory) {
@@ -33,21 +34,20 @@ abstract contract Payable {
33
34
  Values.use(budget, value);
34
35
  }
35
36
 
36
- /// @notice Close a native-value budget and settle any drained value.
37
- /// @param account Account identifier for the current invocation.
38
- /// @param budget Mutable native-value budget to close.
39
- function closeValue(bytes32 account, Budget memory budget) internal {
40
- uint value = Values.drain(budget);
41
- if (value == 0) return;
42
- settleValue(account, value);
43
- }
37
+ /// @notice Drain a native-value budget into a credit-only TRANSACTION block.
38
+ /// @dev Emits `Received` with `Actions.Refund` when a transaction is created.
39
+ /// @param budget Mutable budget whose remaining value is drained.
40
+ /// @param account Destination account to credit with the remaining native value.
41
+ /// @return transaction Encoded TRANSACTION block, or empty bytes when the budget is empty.
42
+ function closeValue(
43
+ Budget memory budget,
44
+ bytes32 account
45
+ ) internal returns (bytes memory transaction) {
46
+ uint amount = Values.drain(budget);
47
+ if (amount == 0) return "";
44
48
 
45
- /// @notice Handle a drained native value amount.
46
- /// @dev Override to refund or redirect unused value. The default rejects it.
47
- /// @param account Account identifier for the current invocation.
48
- /// @param value Drained native value amount to settle, in wei.
49
- function settleValue(bytes32 account, uint value) internal virtual {
50
- account;
51
- revert UnusedValue(value);
49
+ transaction = Cursors.toTransactionBlock(bytes32(0), account, nativeAsset, amount);
50
+ emit Received(account, nativeAsset, amount, Actions.Refund, 0);
52
51
  }
52
+
53
53
  }
package/core/Pipeline.sol CHANGED
@@ -1,34 +1,38 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import {Cursors, Cur} from "../Cursors.sol";
4
+ import {Cursors, Cur, Readers, Reader} from "../Cursors.sol";
5
5
  import {Payable} from "./Payable.sol";
6
+ import {Settlement} from "./Settlement.sol";
6
7
  import {Budget} from "../utils/Value.sol";
7
8
 
8
9
  using Cursors for Cur;
10
+ using Readers for Reader;
9
11
 
10
12
  /// @title Pipeline
11
13
  /// @notice Core pipeline functionality shared by higher-level surfaces.
12
- abstract contract Pipeline is Payable {
14
+ abstract contract Pipeline is Payable, Settlement {
13
15
  /// @dev Thrown when the pipeline finishes with non-empty threaded state.
14
16
  error UnexpectedState();
15
17
 
16
18
  /// @notice Override to dispatch one piped step.
17
- /// Called once per STEP block. The returned bytes become the state passed to
18
- /// the next step, and the final returned state must be empty.
19
+ /// Called once per STEP block. The returned state becomes the state passed to
20
+ /// the next step, and the final returned state must be empty. Returned
21
+ /// transactions are decoded and passed individually to `settle` before the next step runs.
19
22
  /// @param target Node ID to invoke or handle.
20
23
  /// @param account Account identifier for the piped context.
21
24
  /// @param state Current threaded state block stream.
22
25
  /// @param request Step request block stream.
23
26
  /// @param value Native EVM value assigned to this step.
24
- /// @return Updated state block stream for the next step.
27
+ /// @return nextState Updated state block stream for the next step.
28
+ /// @return transactions Transaction block stream produced by the command.
25
29
  function dispatch(
26
30
  uint target,
27
31
  bytes32 account,
28
32
  bytes memory state,
29
33
  bytes calldata request,
30
34
  uint128 value
31
- ) internal virtual returns (bytes memory);
35
+ ) internal virtual returns (bytes memory nextState, bytes memory transactions);
32
36
 
33
37
  /// @notice Execute a STEP block stream through the pipeline.
34
38
  /// @dev Reverts with `UnexpectedState` if the final threaded state is non-empty.
@@ -37,20 +41,19 @@ abstract contract Pipeline is Payable {
37
41
  /// @param state Initial state block stream passed to the first step.
38
42
  /// @param steps STEP block stream to execute.
39
43
  /// @param budget Mutable native-value budget shared across all steps.
40
- function pipe(
41
- bytes32 account,
42
- bytes memory state,
43
- bytes calldata steps,
44
- Budget memory budget
45
- ) internal {
44
+ function pipe(bytes32 account, bytes memory state, bytes calldata steps, Budget memory budget) internal {
46
45
  (Cur memory input, ) = Cursors.init(steps, 1);
47
46
 
48
47
  while (input.i < input.len) {
49
48
  (uint target, uint resources, bytes calldata request) = input.unpackStep();
50
- state = dispatch(target, account, state, request, useValue(budget, resources));
49
+ Reader memory txs;
50
+ (state, txs.source) = dispatch(target, account, state, request, useValue(budget, resources));
51
+ while (txs.more()) {
52
+ (bytes32 from, bytes32 to, bytes32 asset, uint amount) = txs.unpackTransaction();
53
+ settle(from, to, asset, amount);
54
+ }
51
55
  }
52
56
 
53
57
  if (state.length != 0) revert UnexpectedState();
54
- input.complete();
55
58
  }
56
59
  }
@@ -0,0 +1,39 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ pragma solidity ^0.8.33;
3
+
4
+ /// @title DebitAccountHook
5
+ /// @notice Hook for debiting externally managed account funds.
6
+ abstract contract DebitAccountHook {
7
+ /// @notice Override to debit externally managed funds from `account`.
8
+ /// @param account Source account identifier.
9
+ /// @param asset Asset identifier.
10
+ /// @param amount Amount to debit.
11
+ function debitAccount(bytes32 account, bytes32 asset, uint amount) internal virtual;
12
+ }
13
+
14
+ /// @title CreditAccountHook
15
+ /// @notice Hook for crediting externally managed account funds.
16
+ abstract contract CreditAccountHook {
17
+ /// @notice Override to credit externally managed funds to `account`.
18
+ /// @param account Destination account identifier.
19
+ /// @param asset Asset identifier.
20
+ /// @param amount Amount to credit.
21
+ function creditAccount(bytes32 account, bytes32 asset, uint amount) internal virtual;
22
+ }
23
+
24
+ /// @title Settlement
25
+ /// @notice Settles decoded transactions through debit and credit account hooks.
26
+ abstract contract Settlement is DebitAccountHook, CreditAccountHook {
27
+ /// @notice Settle one transaction by debiting its source and crediting its destination.
28
+ /// Returns without calling either hook when `amount` is zero and skips either
29
+ /// operation when the corresponding account is zero.
30
+ /// @param from Source account identifier.
31
+ /// @param to Destination account identifier.
32
+ /// @param asset Asset identifier.
33
+ /// @param amount Token amount.
34
+ function settle(bytes32 from, bytes32 to, bytes32 asset, uint amount) internal {
35
+ if (amount == 0) return;
36
+ if (from != 0) debitAccount(from, asset, amount);
37
+ if (to != 0) creditAccount(to, asset, amount);
38
+ }
39
+ }
package/docs/Schema.md CHANGED
@@ -33,7 +33,7 @@ used. A host can publish the meaning of a custom key with:
33
33
  event Schema(uint indexed host, bytes4 key, string schema, bytes32 name);
34
34
  ```
35
35
 
36
- For example, a host-specific payment block can use `Keys.local(1)`, the command
36
+ For example, a host-specific payment block can use a small literal, the command
37
37
  selector, or any other chosen `bytes4` value as long as that key is not
38
38
  overloaded in the relevant host/schema context.
39
39
 
@@ -65,16 +65,19 @@ A schema body is a comma-separated list of items. Order is significant.
65
65
 
66
66
  ## Payload Layout
67
67
 
68
- A block payload has fixed fields first, followed by an optional child-block tail.
69
- Once a child block appears, no more fixed fields may follow.
68
+ A block payload encodes schema items in declaration order. Fixed fields are
69
+ packed inline, and zero or more child blocks are embedded directly at their
70
+ declared positions. Because every child block carries its own header and payload
71
+ length, fixed fields may appear before, after, or between child blocks.
70
72
 
71
73
  ```txt
72
74
  { uint target, uint resources, #bytes as payload }
73
75
  { bytes32 account, #bytes as state, #bytes as request }
76
+ { bytes4 key, #string as body, bytes32 name }
77
+ { #bytes as left, uint op, #bytes as right }
74
78
  ```
75
79
 
76
- The tail is embedded directly as child block bytes. There is no wrapper around a
77
- child-block tail.
80
+ There is no wrapper around embedded child blocks.
78
81
 
79
82
  Raw dynamic bytes are represented with the reserved `#bytes` child block. Use an
80
83
  alias to give those bytes a presentation name:
@@ -293,19 +296,21 @@ invalid in any path segment.
293
296
  ## Reserved Blocks
294
297
 
295
298
  - `#bytes`: raw dynamic bytes, written without a body
299
+ - `#string`: UTF-8 string bytes, written without a body
296
300
  - `#list`: generic list wrapper emitted by `many`
297
301
 
298
302
  Custom input shapes should define their own context-local block key and publish
299
- that key with a `Schema` event:
303
+ that key with a `Schema` event. Endpoint contracts can use `schema(...)` for
304
+ that publication:
300
305
 
301
306
  ```solidity
302
- bytes4 constant Input = Keys.local(1);
303
- emit Schema(host, Input, "{ bytes32 asset, uint amount }", bytes32("payment"));
307
+ bytes4 input = schema(1, "{ bytes32 asset, uint amount }");
304
308
  ```
305
309
 
306
- The key can be a small literal, a selector, or any other `bytes4` value that is
307
- unique in the context where it is used. The alias names the block; the schema
308
- string describes only the payload body.
310
+ Use different numeric keys when a host needs more than one local block key. The
311
+ key can also be a selector or any other `bytes4` value that is unique in the
312
+ context where it is used. The alias names the block; the schema string describes
313
+ only the payload body.
309
314
 
310
315
  ## Standard Blocks
311
316
 
@@ -320,6 +325,7 @@ step { uint target, uint resources, #bytes as request }
320
325
  context { bytes32 account, #bytes as state, #bytes as request }
321
326
  recover { uint handler, uint resources, bytes32 key, #bytes as witness }
322
327
  auth { uint cid, uint deadline, #bytes as proof }
328
+ schema { bytes4 key, #string as body, bytes32 name }
323
329
  ```
324
330
 
325
331
  `Keys.sol` contains the corresponding standard runtime keys.
@@ -5,11 +5,12 @@ 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 blocknum)";
8
+ string private constant ABI = "event Introduction(uint indexed host, uint peer, uint blocknum)";
9
9
 
10
- /// @param host Host node ID of the introducing contract.
10
+ /// @param host Host node ID receiving the introduction.
11
+ /// @param peer Host node ID of the introducing contract.
11
12
  /// @param blocknum Block number at which the host was deployed.
12
- event Introduction(uint indexed host, uint blocknum);
13
+ event Introduction(uint indexed host, uint peer, uint blocknum);
13
14
 
14
15
  constructor() {
15
16
  emit EventAbi(ABI);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rootzero/contracts",
3
- "version": "1.11.0",
3
+ "version": "1.13.0",
4
4
  "description": "Solidity contracts and protocol building blocks for rootzero hosts and commands.",
5
5
  "private": false,
6
6
  "license": "GPL-3.0-only",
package/ports/Credit.sol CHANGED
@@ -2,7 +2,7 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import { PortBase } from "./Base.sol";
5
- import { CreditAccountHook } from "../commands/Credit.sol";
5
+ import { CreditAccountHook } from "../core/Settlement.sol";
6
6
  import { Cursors, Cur, Keys } from "../Cursors.sol";
7
7
 
8
8
  using Cursors for Cur;
package/ports/Debit.sol CHANGED
@@ -2,7 +2,7 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import { PortBase } from "./Base.sol";
5
- import { DebitAccountHook } from "../commands/Debit.sol";
5
+ import { DebitAccountHook } from "../core/Settlement.sol";
6
6
  import { Cursors, Cur, Keys } from "../Cursors.sol";
7
7
 
8
8
  using Cursors for Cur;
package/ports/Settle.sol CHANGED
@@ -2,8 +2,7 @@
2
2
  pragma solidity ^0.8.33;
3
3
 
4
4
  import { PortBase } from "./Base.sol";
5
- import { CreditAccountHook } from "../commands/Credit.sol";
6
- import { DebitAccountHook } from "../commands/Debit.sol";
5
+ import { Settlement } from "../core/Settlement.sol";
7
6
  import { Cursors, Cur, Keys } from "../Cursors.sol";
8
7
 
9
8
  using Cursors for Cur;
@@ -11,7 +10,7 @@ using Cursors for Cur;
11
10
  /// @title PortSettle
12
11
  /// @notice Port that consumes peer-supplied TRANSACTION blocks through debit and credit hooks.
13
12
  /// Each TRANSACTION block calls `debitAccount` for `from` and `creditAccount` for `to`.
14
- abstract contract PortSettle is PortBase, DebitAccountHook, CreditAccountHook {
13
+ abstract contract PortSettle is PortBase, Settlement {
15
14
  bytes32 private immutable descriptor;
16
15
 
17
16
  constructor() {
@@ -26,8 +25,7 @@ abstract contract PortSettle is PortBase, DebitAccountHook, CreditAccountHook {
26
25
 
27
26
  while (input.i < input.len) {
28
27
  (bytes32 from, bytes32 to, bytes32 asset, uint amount) = input.unpackTransaction();
29
- if (from != 0) debitAccount(from, asset, amount);
30
- if (to != 0) creditAccount(to, asset, amount);
28
+ settle(from, to, asset, amount);
31
29
  }
32
30
  return "";
33
31
  }