@rootzero/contracts 1.23.0 → 1.24.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 CHANGED
@@ -3,6 +3,45 @@
3
3
  Until the protocol reaches integration-stable status, minor versions may include
4
4
  breaking API changes. Breaking changes are called out explicitly.
5
5
 
6
+ Add all changes made after a release to `Unreleased`. Published version
7
+ sections are immutable and must continue to describe the tagged release.
8
+
9
+ ## Unreleased
10
+
11
+ ## 1.24.0
12
+
13
+ ### Breaking Changes
14
+
15
+ - Renamed the existing `Position`-consuming `repay` and `repayPayable`
16
+ commands to `repayPosition` and `repayPositionPayable`. Their state
17
+ transition remains `Position` to `Balance`; the new commands using the old
18
+ names consume `Debt` and return empty state. Command selectors and node IDs
19
+ for position repayment therefore change.
20
+
21
+ ### Added
22
+
23
+ - Added the standalone `Debt { liability, debt }` pipeline state, its standard
24
+ block key and schema, and codec support across blocks, readers, writers,
25
+ decoders, and execution helpers.
26
+ - Added `repay` and `repayPayable` commands that consume `Debt` state and return
27
+ empty state.
28
+ - Added `RepayInternal` for dispatching the non-funded `repay` command directly
29
+ against memory-backed pipeline state.
30
+ - Added unchecked `Blocks.read1`, `read2`, `read8`, and `read16` absolute
31
+ calldata helpers, completing the power-of-two family alongside `read4` and
32
+ `read32`.
33
+ - Added the standalone `ensureContract` utility and shared `InvalidContract`
34
+ error for uniformly requiring addresses with deployed bytecode.
35
+ - Added the free `enforceSender` access helper for requiring an exact
36
+ `msg.sender` with the shared `AccessDenied` error.
37
+ - Documented descriptor flag bits 6 and 7 as endpoint-defined custom flags;
38
+ bits 2 through 5 remain reserved for future protocol flags.
39
+
40
+ ### Changed
41
+
42
+ - Repayment commands now publish the canonical `Actions.Repay` annotation
43
+ instead of `Actions.Settle`.
44
+
6
45
  ## 1.23.0
7
46
 
8
47
  ### Breaking Changes
package/Codec.sol CHANGED
@@ -4,7 +4,7 @@ pragma solidity ^0.8.33;
4
4
  // Aggregator: re-exports the complete block encoding and decoding surface.
5
5
  // Import this file for low-level codec extensions and direct stream processing.
6
6
 
7
- import { AssetAmount, AccountAsset, HostAsset, AccountAmount, HostAmount, HostAccountAsset, HostAccountAmount, Position, Tx } from "./core/Types.sol";
7
+ import { AssetAmount, AccountAsset, HostAsset, AccountAmount, HostAmount, HostAccountAsset, HostAccountAmount, Debt, Position, Tx } from "./core/Types.sol";
8
8
  import { Keys } from "./codec/Keys.sol";
9
9
  import { Sizes, Specs } from "./codec/Specs.sol";
10
10
  import { Descriptors, Flags } from "./codec/Descriptors.sol";
package/Commands.sol CHANGED
@@ -12,4 +12,4 @@ import {Blocks} from "./codec/Blocks.sol";
12
12
  import {Sizes, Specs} from "./codec/Specs.sol";
13
13
  import {Decoders} from "./codec/Decoders.sol";
14
14
  import {Cursors, Cur} from "./utils/Cursors.sol";
15
- import {AssetAmount, AccountAsset, HostAsset, AccountAmount, HostAmount, HostAccountAsset, HostAccountAmount, Position, Tx} from "./core/Types.sol";
15
+ import {AssetAmount, AccountAsset, HostAsset, AccountAmount, HostAmount, HostAccountAsset, HostAccountAmount, Debt, Position, Tx} from "./core/Types.sol";
package/Core.sol CHANGED
@@ -7,7 +7,7 @@ pragma solidity ^0.8.33;
7
7
  import { Action } from "./annotations/Action.sol";
8
8
  import { Label } from "./annotations/Label.sol";
9
9
  import { Schema } from "./annotations/Schema.sol";
10
- import { AccessDenied, AdminAccess, CallerAccess, CommanderAccess, CommanderNotAllowed, GuardianAccess, NodeAccess, TrustAccess } from "./core/Access.sol";
10
+ import { AccessDenied, AdminAccess, CallerAccess, CommanderAccess, CommanderNotAllowed, enforceSender, GuardianAccess, NodeAccess, TrustAccess } from "./core/Access.sol";
11
11
  import { Balances, InsufficientFunds } from "./core/Balances.sol";
12
12
  import { Escrows, InsufficientEscrow } from "./core/Escrows.sol";
13
13
  import { NativeAsset, Runtime } from "./core/Runtime.sol";
@@ -18,7 +18,7 @@ import { Pipeline } from "./core/Pipeline.sol";
18
18
  import { Budget, Budgets } from "./execution/Budget.sol";
19
19
  import { CreditAccountHook, DebitAccountHook, PostHook, RepayHook, SettleHook, Settlement } from "./core/Settlement.sol";
20
20
  import { Portal } from "./core/Portal.sol";
21
- import { AssetAmount, AccountAsset, HostAsset, AccountAmount, HostAmount, HostAccountAsset, HostAccountAmount, Position, Tx } from "./core/Types.sol";
21
+ import { AssetAmount, AccountAsset, HostAsset, AccountAmount, HostAmount, HostAccountAsset, HostAccountAmount, Debt, Position, Tx } from "./core/Types.sol";
22
22
  import { Validator } from "./core/Validator.sol";
23
23
 
24
24
 
package/Endpoints.sol CHANGED
@@ -18,7 +18,14 @@ import {Deposit, DepositHook, DepositPayable, DepositPayableHook} from "./comman
18
18
  import {Payout, PayoutHook} from "./commands/Payout.sol";
19
19
  import {Provision, ProvisionHook, ProvisionPayable, ProvisionPayableHook} from "./commands/Provision.sol";
20
20
  import {RecoverPayable, RecoverPayableHook} from "./commands/Recover.sol";
21
- import {Repay, RepayPayable, RepayPayableHook} from "./commands/Repay.sol";
21
+ import {
22
+ Repay,
23
+ RepayInternal,
24
+ RepayPayable,
25
+ RepayPayableHook,
26
+ RepayPosition,
27
+ RepayPositionPayable
28
+ } from "./commands/Repay.sol";
22
29
  import {RelayPayable, RelayBalancePayable, RelayPayableHook} from "./commands/Relay.sol";
23
30
  import {Settle, SettlePayable, SettlePayableHook, SettleInternal} from "./commands/Settle.sol";
24
31
  import {Withdraw, WithdrawHook} from "./commands/Withdraw.sol";
package/README.md CHANGED
@@ -265,22 +265,24 @@ and return it, forward it intact, or revert. A command must never succeed while
265
265
  silently ignoring or dropping supplied state. Commands that declare
266
266
  `Specs.Empty` state therefore reject any non-empty state, while commands that
267
267
  accept state validate the complete stream against their declared state schema.
268
- This is especially important for `#position`, because dropping a position could
269
- silently discard both live value and an outstanding debt requirement.
270
-
271
- The input carries instructions; the state carries live value. While a
272
- sequence of commands executes, `#balance`, `#custody`, and `#position` blocks
273
- in the state are the value being moved — produced by one command, consumed by
274
- the next. A position carries an asset-liability pair as
268
+ This is especially important for `#debt` and `#position`, because dropping
269
+ either could silently discard an outstanding debt requirement.
270
+
271
+ The input carries instructions; the state carries live value. While a sequence
272
+ of commands executes, `#balance`, `#debt`, `#custody`, and `#position` blocks in
273
+ the state are the value being moved — produced by one command, consumed by the
274
+ next. Balance carries `{ asset, amount }`, debt carries `{ liability, debt }`,
275
+ and position carries their flat combination
275
276
  `{ asset, amount, liability, debt }`.
276
277
 
277
- `#position` is general live state rather than a lending-specific debt record.
278
- It pairs value acquired or controlled with value owed or required. A command
279
- may preserve or replace either side and return the resulting position for the
280
- next step; `settle` terminally consumes the pair. This supports swaps,
278
+ `#debt` and `#position` are general live state rather than persisted
279
+ lending-specific debt records. Debt carries value owed or required; position
280
+ pairs that liability with value acquired or controlled. A command may preserve
281
+ or replace either side and return the resulting state for the next step;
282
+ `settle` terminally consumes a position pair. This supports swaps,
281
283
  borrowing, refinancing, collateral changes, callback obligations, cross-host
282
- claims, fees, netting, and other multi-step operations. A position is a
283
- transient representation and does not itself create or erase an obligation
284
+ claims, fees, netting, and other multi-step operations. Debt and position are
285
+ transient representations and do not themselves create or erase an obligation
284
286
  recorded by an external system.
285
287
 
286
288
  The standard `Deposit` mixin shows the canonical shape: open and validate both
@@ -330,7 +332,9 @@ abstract contract MyCommand is CommandBase {
330
332
 
331
333
  The final argument is a packed flags byte. Pass `0` for an ordinary endpoint,
332
334
  or compose values such as `Flags.Funded`, `Flags.Admin`, and
333
- `Flags.AdminFunded` from the command or endpoint package entry point.
335
+ `Flags.AdminFunded` from the command or endpoint package entry point. Bits 6
336
+ and 7 are reserved for endpoint-defined custom flags. Bits 2 through 5 remain
337
+ reserved for future protocol flags.
334
338
 
335
339
  The standard commands cover the common ledger movements: `deposit` and
336
340
  `depositPayable` (external funds in), `settlePayable` (funded settlement),
@@ -338,7 +342,9 @@ The standard commands cover the common ledger movements: `deposit` and
338
342
  `debitAccount` and `creditAccount` (internal movements), `payout` (deliver
339
343
  state to other accounts), `allocate` (turn balance state into custody),
340
344
  `provision` (provision custody from an external allocation), `settle` (consume
341
- asset-liability position state), `relayPayable` (relay a pipeline without
345
+ asset-liability position state), `repay` and `repayPayable` (consume standalone
346
+ debt state), `repayPosition` and `repayPositionPayable` (repay a position's debt
347
+ and return its asset as balance state), `relayPayable` (relay a pipeline without
342
348
  state), and `relayBalancePayable` (relay balance state and a pipeline to another
343
349
  portal).
344
350
 
@@ -384,12 +390,12 @@ portal adapter (on EVM, the low 128 bits are native value in wei, drawn from a
384
390
  shared budget), so the same pipeline bytes are meaningful to every port.
385
391
 
386
392
  Hosts that implement a pipeline locally can inherit `DebitAccountInternal`,
387
- `CreditAccountInternal`, and `SettleInternal` to advertise the canonical command
388
- endpoints while routing their local command IDs through `executeDebitAccount`,
389
- `executeCreditAccount`, and `executeSettle`. These adapters consume the
390
- memory-backed pipeline state directly and avoid an external self-call. Pass the
391
- step value into each adapter; all three reject nonzero value because the
392
- commands are non-funded.
393
+ `CreditAccountInternal`, `SettleInternal`, and `RepayInternal` to advertise the
394
+ canonical command endpoints while routing their local command IDs through
395
+ `executeDebitAccount`, `executeCreditAccount`, `executeSettle`, and
396
+ `executeRepay`. These adapters consume the memory-backed pipeline state directly
397
+ and avoid an external self-call. Pass the step value into each adapter; all four
398
+ reject nonzero value because the commands are non-funded.
393
399
 
394
400
  Positions also support backward-composed pipelines. In an exact-output route,
395
401
  the asset side can represent the desired result while the liability side
package/Utils.sol CHANGED
@@ -12,7 +12,7 @@ import { ECDSA } from "./utils/ECDSA.sol";
12
12
  import { Ids } from "./utils/Ids.sol";
13
13
  import { Nodes } from "./utils/Nodes.sol";
14
14
  import { Layout } from "./utils/Layout.sol";
15
- import { addrOr, applyBps, beforeBps, bytes32ToInt, bytes32ToString, clear8, clear16, clear32, clear64, divisible, ensureAddr, hash32, intToBytes32, isFamily, matchesBase, MAX_BPS, max8, max16, max24, max32, max40, max64, max96, max128, max160, NotDivisible, replace8, replace16, replace32, replace64, retryTicket, toLocalBase, toUnspecifiedBase, ValueOverflow, ZeroAddress } from "./utils/Utils.sol";
15
+ import { addrOr, applyBps, beforeBps, bytes32ToInt, bytes32ToString, clear8, clear16, clear32, clear64, divisible, ensureAddr, ensureContract, hash32, intToBytes32, InvalidContract, isFamily, matchesBase, MAX_BPS, max8, max16, max24, max32, max40, max64, max96, max128, max160, NotDivisible, replace8, replace16, replace32, replace64, retryTicket, toLocalBase, toUnspecifiedBase, ValueOverflow, ZeroAddress } from "./utils/Utils.sol";
16
16
 
17
17
 
18
18
 
@@ -18,13 +18,7 @@ abstract contract Schema is Runtime, AnnotationEvent {
18
18
  /// @param hint Initial per-block payload capacity.
19
19
  /// @param body Schema DSL string describing the block payload body.
20
20
  /// @return spec The context-local block specification.
21
- function schema(
22
- uint32 key,
23
- uint32 min,
24
- uint32 max,
25
- uint32 hint,
26
- string memory body
27
- ) internal returns (uint spec) {
21
+ function schema(uint32 key, uint32 min, uint32 max, uint32 hint, string memory body) internal returns (uint spec) {
28
22
  return schema(key, min, max, hint, body, bytes32(0));
29
23
  }
30
24
 
package/codec/Blocks.sol CHANGED
@@ -382,6 +382,22 @@ library Blocks {
382
382
  }
383
383
  }
384
384
 
385
+ /// @notice Write a DEBT block at `i`.
386
+ /// @dev DANGER: Unchecked memory write. Reserve `Sizes.B64` bytes first.
387
+ /// @param dst Destination buffer.
388
+ /// @param i Relative write position.
389
+ /// @param liability Liability identifier to encode.
390
+ /// @param debt Debt quantity to encode.
391
+ function writeDebt(bytes memory dst, uint i, bytes32 liability, uint debt) internal pure {
392
+ uint spec = Specs.Debt;
393
+ assembly ("memory-safe") {
394
+ let p := add(add(dst, 0x20), i)
395
+ mstore(p, spec)
396
+ mstore(add(p, 0x08), liability)
397
+ mstore(add(p, 0x28), debt)
398
+ }
399
+ }
400
+
385
401
  /// @notice Write a HOST_ASSET block at `i`.
386
402
  /// @dev DANGER: Unchecked memory write. Reserve `Sizes.B64` bytes first.
387
403
  /// @param dst Destination buffer.
@@ -1014,14 +1030,44 @@ library Blocks {
1014
1030
 
1015
1031
  // Raw reads
1016
1032
 
1033
+ /// @notice Read one byte from an absolute calldata position.
1034
+ /// @dev DANGER: Unchecked calldata read. Values beyond calldata are zero-padded.
1035
+ /// @param abs Absolute calldata position.
1036
+ /// @return value Decoded one-byte value.
1037
+ function read1(uint abs) internal pure returns (bytes1 value) {
1038
+ return bytes1(read32(abs));
1039
+ }
1040
+
1041
+ /// @notice Read two bytes from an absolute calldata position.
1042
+ /// @dev DANGER: Unchecked calldata read. Values beyond calldata are zero-padded.
1043
+ /// @param abs Absolute calldata position.
1044
+ /// @return value Decoded two-byte value.
1045
+ function read2(uint abs) internal pure returns (bytes2 value) {
1046
+ return bytes2(read32(abs));
1047
+ }
1048
+
1017
1049
  /// @notice Read four bytes from an absolute calldata position.
1018
1050
  /// @dev DANGER: Unchecked calldata read. Values beyond calldata are zero-padded.
1019
1051
  /// @param abs Absolute calldata position.
1020
1052
  /// @return value Decoded four-byte value.
1021
1053
  function read4(uint abs) internal pure returns (bytes4 value) {
1022
- assembly ("memory-safe") {
1023
- value := calldataload(abs)
1024
- }
1054
+ return bytes4(read32(abs));
1055
+ }
1056
+
1057
+ /// @notice Read eight bytes from an absolute calldata position.
1058
+ /// @dev DANGER: Unchecked calldata read. Values beyond calldata are zero-padded.
1059
+ /// @param abs Absolute calldata position.
1060
+ /// @return value Decoded eight-byte value.
1061
+ function read8(uint abs) internal pure returns (bytes8 value) {
1062
+ return bytes8(read32(abs));
1063
+ }
1064
+
1065
+ /// @notice Read sixteen bytes from an absolute calldata position.
1066
+ /// @dev DANGER: Unchecked calldata read. Values beyond calldata are zero-padded.
1067
+ /// @param abs Absolute calldata position.
1068
+ /// @return value Decoded sixteen-byte value.
1069
+ function read16(uint abs) internal pure returns (bytes16 value) {
1070
+ return bytes16(read32(abs));
1025
1071
  }
1026
1072
 
1027
1073
  /// @notice Read one word from an absolute calldata position.
@@ -1322,6 +1368,22 @@ library Blocks {
1322
1368
  }
1323
1369
  }
1324
1370
 
1371
+ /// @notice Decode a low-level fixed-width DEBT block at `abs`.
1372
+ /// @param abs Absolute block position.
1373
+ /// @return liability Decoded liability identifier.
1374
+ /// @return debt Decoded debt quantity.
1375
+ function unpackDebt(uint abs) internal pure returns (bytes32 liability, uint debt) {
1376
+ uint head;
1377
+ assembly ("memory-safe") {
1378
+ head := calldataload(abs)
1379
+ }
1380
+ if (head >> 192 != Specs.Debt >> 192) revert InvalidBlock();
1381
+ assembly ("memory-safe") {
1382
+ liability := calldataload(add(abs, 0x08))
1383
+ debt := calldataload(add(abs, 0x28))
1384
+ }
1385
+ }
1386
+
1325
1387
  /// @notice Decode a low-level fixed-width ACCOUNT_ASSET block at `abs`.
1326
1388
  /// @param abs Absolute block position.
1327
1389
  /// @return account Decoded account identifier.
@@ -1919,6 +1981,15 @@ library Blocks {
1919
1981
  writeBalance(value, 0, asset, amount);
1920
1982
  }
1921
1983
 
1984
+ /// @notice Encode a DEBT block.
1985
+ /// @param liability Liability identifier.
1986
+ /// @param debt Debt quantity.
1987
+ /// @return value Encoded DEBT block bytes.
1988
+ function createDebt(bytes32 liability, uint debt) internal pure returns (bytes memory value) {
1989
+ value = allocate(Sizes.Debt);
1990
+ writeDebt(value, 0, liability, debt);
1991
+ }
1992
+
1922
1993
  /// @notice Encode a CUSTODY block.
1923
1994
  /// @param host Host node ID holding the custody.
1924
1995
  /// @param asset Asset identifier.
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import {AssetAmount, AccountAsset, HostAsset, AccountAmount, HostAmount, HostAccountAsset, Position, Tx} from "../core/Types.sol";
4
+ import {AssetAmount, AccountAsset, HostAsset, AccountAmount, HostAmount, HostAccountAsset, Debt, Position, Tx} from "../core/Types.sol";
5
5
  import {Blocks} from "./Blocks.sol";
6
6
  import {Sizes, Specs} from "./Specs.sol";
7
7
  import {Cursors, Cur} from "../utils/Cursors.sol";
@@ -427,6 +427,16 @@ library Decoders {
427
427
  (asset, amount) = Blocks.unpackBalance(abs);
428
428
  }
429
429
 
430
+ /// @notice Decode and consume one DEBT block.
431
+ /// @param cur Cursor advanced past the block.
432
+ /// @return liability Decoded liability identifier.
433
+ /// @return debt Decoded debt quantity.
434
+ function unpackDebt(Cur memory cur) internal pure returns (bytes32 liability, uint debt) {
435
+ uint abs;
436
+ (cur.state, abs) = cur.state.consume(Sizes.Debt);
437
+ (liability, debt) = Blocks.unpackDebt(abs);
438
+ }
439
+
430
440
  /// @notice Decode one BALANCE block and associate it with `host`.
431
441
  /// @param cur Cursor advanced past the block.
432
442
  /// @param host Host identifier associated with the balance.
@@ -614,6 +624,13 @@ library Decoders {
614
624
  (value.asset, value.amount) = unpackBalance(cur);
615
625
  }
616
626
 
627
+ /// @notice Decode one DEBT block into its structured value.
628
+ /// @param cur Cursor advanced past the block.
629
+ /// @return value Structured liability and debt.
630
+ function unpackDebtValue(Cur memory cur) internal pure returns (Debt memory value) {
631
+ (value.liability, value.debt) = unpackDebt(cur);
632
+ }
633
+
617
634
  /// @notice Decode and consume one HOST_ACCOUNT_ASSET block.
618
635
  /// @param cur Cursor advanced past the block.
619
636
  /// @return host Decoded host identifier.
@@ -6,6 +6,7 @@ import {Lanes} from "../utils/Lanes.sol";
6
6
 
7
7
  /// @title Flags
8
8
  /// @notice Packed endpoint behavior flags.
9
+ /// @dev Bits 6 and 7 are reserved for endpoint-defined custom flags.
9
10
  library Flags {
10
11
  /// @dev Endpoint accepts nonzero native value.
11
12
  uint8 internal constant Funded = 1 << 0;
@@ -27,7 +28,8 @@ library Descriptors {
27
28
  /// `[output key:4][min:4][max:4][hint:3][stride:1]`
28
29
  /// `[reserved:4]`
29
30
  /// `[transactions:1]`
30
- /// `[flags:1]`. Flag bits: funded = 0, admin = 1.
31
+ /// `[flags:1]`. Flag bits: funded = 0, admin = 1; bits 6 and 7 are
32
+ /// reserved for endpoint-defined custom flags.
31
33
  /// @param state State lane specification.
32
34
  /// @param input Direct input lane specification.
33
35
  /// @param output Output writer specification.
package/codec/Keys.sol CHANGED
@@ -13,6 +13,8 @@ library Keys {
13
13
  bytes4 constant Amount = bytes4(keccak256("#amount"));
14
14
  /// @dev Ledger balance - (bytes32 asset, uint amount)
15
15
  bytes4 constant Balance = bytes4(keccak256("#balance"));
16
+ /// @dev Liability-only debt state - (bytes32 liability, uint debt)
17
+ bytes4 constant Debt = bytes4(keccak256("#debt"));
16
18
  /// @dev Host-scoped input amount - (uint host, bytes32 asset, uint amount)
17
19
  bytes4 constant Allocation = bytes4(keccak256("#allocation"));
18
20
  /// @dev Host-scoped allowance cap - (uint host, bytes32 asset, uint amount)
package/codec/Readers.sol CHANGED
@@ -133,6 +133,18 @@ library Readers {
133
133
  }
134
134
  }
135
135
 
136
+ /// @notice Consume a DEBT block and return its fields.
137
+ /// @param cur Reader; advanced past the block.
138
+ /// @return liability Liability identifier.
139
+ /// @return debt Debt quantity.
140
+ function unpackDebt(Reader memory cur) internal pure returns (bytes32 liability, uint debt) {
141
+ uint abs = consume(cur, Keys.Debt, 64, 64);
142
+ assembly ("memory-safe") {
143
+ liability := mload(abs)
144
+ debt := mload(add(abs, 0x20))
145
+ }
146
+ }
147
+
136
148
  /// @notice Consume a HOST_ASSET block and return its fields.
137
149
  /// @param cur Reader; advanced past the block.
138
150
  /// @return host Host identifier.
package/codec/Schema.sol CHANGED
@@ -53,12 +53,12 @@ pragma solidity ^0.8.33;
53
53
  // - command input and state streams are each a single run of blocks under the
54
54
  // current protocol convention; the block format may support other shapes in
55
55
  // future protocol surfaces
56
- // - `balance(...)`, `custody(...)`, and `position(...)` are live, linear state in the active command pipeline
56
+ // - `balance(...)`, `debt(...)`, `custody(...)`, and `position(...)` are live, linear state in the active command pipeline
57
57
  // - pipeline state belongs to the active account while the pipeline is executing
58
- // - while a balance or custody is in-flight as pipeline state, it is not simultaneously persisted
58
+ // - while a balance, debt, or custody is in-flight as pipeline state, it is not simultaneously persisted
59
59
  // in another ledger/store by this protocol
60
- // - a position pairs live asset and liability sides; commands may transform either side
61
- // - position state is transient and does not itself create or erase an externally persisted obligation
60
+ // - debt carries only a live liability side; position pairs live balance and debt sides
61
+ // - debt and position state are transient and do not themselves create or erase an externally persisted obligation
62
62
  // - positions support backward composition, but pipeline steps always execute in encoded order
63
63
  // - commands must preserve, transform, settle, or intentionally consume pipeline state
64
64
  // - input blocks such as `amount(...)`, `allocation(...)`, and `allowance(...)`
@@ -89,6 +89,7 @@ library Schemas {
89
89
 
90
90
  string constant Amount = "bytes32 asset, uint amount";
91
91
  string constant Balance = "bytes32 asset, uint amount";
92
+ string constant Debt = "bytes32 liability, uint debt";
92
93
  string constant AccountAsset = "bytes32 account, bytes32 asset";
93
94
  string constant HostAsset = "uint host, bytes32 asset";
94
95
 
package/codec/Specs.sol CHANGED
@@ -27,6 +27,8 @@ library Sizes {
27
27
  uint constant Amount = B64;
28
28
  /// @dev BALANCE block: 8 header + 32 asset + 32 amount = 72 bytes
29
29
  uint constant Balance = B64;
30
+ /// @dev DEBT block: 8 header + 32 liability + 32 debt = 72 bytes
31
+ uint constant Debt = B64;
30
32
  /// @dev HOST_ASSET block: 8 header + 32 host + 32 asset = 72 bytes
31
33
  uint constant HostAsset = B64;
32
34
  /// @dev ALLOCATION/CUSTODY block: 8 header + 32 host + 32 asset + 32 amount = 104 bytes
@@ -63,6 +65,7 @@ library Specs {
63
65
  uint constant Empty = uint(bytes32(Keys.Empty));
64
66
  uint constant Amount = uint(bytes32(Keys.Amount)) | Exact64;
65
67
  uint constant Balance = uint(bytes32(Keys.Balance)) | Exact64;
68
+ uint constant Debt = uint(bytes32(Keys.Debt)) | Exact64;
66
69
  uint constant Allocation = uint(bytes32(Keys.Allocation)) | Exact96;
67
70
  uint constant Allowance = uint(bytes32(Keys.Allowance)) | Exact96;
68
71
  uint constant Custody = uint(bytes32(Keys.Custody)) | Exact96;
package/codec/Writers.sol CHANGED
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import {AssetAmount, AccountAmount, HostAmount, Position, Tx} from "../core/Types.sol";
4
+ import {AssetAmount, AccountAmount, HostAmount, Debt, Position, Tx} from "../core/Types.sol";
5
5
  import {Blocks} from "./Blocks.sol";
6
6
  import {Buffers} from "./Buffers.sol";
7
7
  import {Sizes, Specs} from "./Specs.sol";
@@ -204,6 +204,17 @@ library Writers {
204
204
  appendBalance(writer, value.asset, value.amount);
205
205
  }
206
206
 
207
+ /// @notice Append a DEBT block.
208
+ function appendDebt(Writer memory writer, bytes32 liability, uint debt) internal pure {
209
+ uint i = reserve(writer, Sizes.Debt);
210
+ Blocks.writeDebt(writer.dst, i, liability, debt);
211
+ }
212
+
213
+ /// @notice Append a structured DEBT value.
214
+ function appendDebt(Writer memory writer, Debt memory value) internal pure {
215
+ appendDebt(writer, value.liability, value.debt);
216
+ }
217
+
207
218
  /// @notice Append an ACCOUNT_ASSET block.
208
219
  /// @param writer Destination writer.
209
220
  /// @param account Account identifier to encode.
@@ -5,10 +5,13 @@ import {Execution, Executions, CommandBase, Flags, Lanes, Specs} from "./Base.so
5
5
  import {RepayHook} from "../core/Settlement.sol";
6
6
  import {Action} from "../annotations/Action.sol";
7
7
  import {Actions} from "../utils/Actions.sol";
8
+ import {Blocks} from "../codec/Blocks.sol";
9
+ import {Reader, Readers} from "../codec/Readers.sol";
8
10
 
9
11
  using Executions for Execution;
12
+ using Readers for Reader;
10
13
 
11
- /// @notice Hook implemented by hosts that repay position liabilities using native value.
14
+ /// @notice Hook implemented by hosts that repay liabilities using native value.
12
15
  abstract contract RepayPayableHook {
13
16
  /// @notice Override to repay one liability for `account` with a shared value budget.
14
17
  /// @param account Account whose liability is being repaid.
@@ -19,21 +22,84 @@ abstract contract RepayPayableHook {
19
22
  }
20
23
 
21
24
  /// @title Repay
22
- /// @notice Command that repays POSITION liabilities and returns their assets as BALANCE state.
25
+ /// @notice Command that consumes DEBT state by repaying each liability.
23
26
  abstract contract Repay is CommandBase, RepayHook, Action {
24
27
  uint private immutable descriptor;
28
+ uint private immutable id;
29
+
30
+ constructor() {
31
+ (id, descriptor) = command("repay", Specs.Debt, Specs.Empty, Specs.Empty, 0, 0);
32
+ action(id, Actions.Repay);
33
+ }
34
+
35
+ /// @notice Return the registered REPAY command ID.
36
+ function repayId() internal view returns (uint) {
37
+ return id;
38
+ }
39
+
40
+ /// @notice Repay each liability in the DEBT state stream.
41
+ /// @param context Command context carrying the DEBT state stream.
42
+ /// @return Empty output state.
43
+ /// @return Empty transaction stream.
44
+ function repay(
45
+ bytes calldata context
46
+ ) external onlyCommand returns (bytes memory, bytes memory) {
47
+ Execution memory exec = openCommand(context, descriptor, 0);
48
+
49
+ while (exec.more()) {
50
+ (bytes32 liability, uint debt) = exec.unpackDebt(Lanes.State);
51
+ repay(exec.account, liability, debt);
52
+ }
53
+
54
+ return closeCommand(exec);
55
+ }
56
+ }
57
+
58
+ /// @title RepayPayable
59
+ /// @notice Funded command that consumes DEBT state by repaying each liability.
60
+ abstract contract RepayPayable is CommandBase, RepayPayableHook, Action {
61
+ uint private immutable descriptor;
25
62
 
26
63
  constructor() {
27
64
  uint id;
28
- (id, descriptor) = command("repay", Specs.Position, Specs.Empty, Specs.Balance, 0, 0);
29
- action(id, Actions.Settle);
65
+ (id, descriptor) = command("repayPayable", Specs.Debt, Specs.Empty, Specs.Empty, 0, Flags.Funded);
66
+ action(id, Actions.Repay);
67
+ }
68
+
69
+ /// @notice Repay each liability in the DEBT state stream.
70
+ /// @param context Command context carrying the DEBT state stream.
71
+ /// @return Empty output state.
72
+ /// @return Remaining native value as a refund transaction stream.
73
+ function repayPayable(
74
+ bytes calldata context
75
+ ) external payable onlyCommand returns (bytes memory, bytes memory) {
76
+ Execution memory exec = openCommand(context, descriptor, 0);
77
+
78
+ while (exec.more()) {
79
+ (bytes32 liability, uint debt) = exec.unpackDebt(Lanes.State);
80
+ repay(exec.account, liability, debt, exec);
81
+ }
82
+
83
+ return closeCommand(exec);
84
+ }
85
+ }
86
+
87
+ /// @title RepayPosition
88
+ /// @notice Command that repays POSITION liabilities and returns their assets as BALANCE state.
89
+ abstract contract RepayPosition is CommandBase, RepayHook, Action {
90
+ uint private immutable descriptor;
91
+
92
+ constructor() {
93
+ uint id;
94
+ (id, descriptor) = command("repayPosition", Specs.Position, Specs.Empty, Specs.Balance, 0, 0);
95
+ action(id, Actions.Repay);
30
96
  }
31
97
 
32
98
  /// @notice Repay each POSITION liability and return its asset as BALANCE state.
33
99
  /// @param context Command context carrying the POSITION state stream.
34
100
  /// @return BALANCE output state containing each released asset side.
35
101
  /// @return Empty transaction stream.
36
- function repay(
102
+ function repayPosition(
37
103
  bytes calldata context
38
104
  ) external onlyCommand returns (bytes memory, bytes memory) {
39
105
  Execution memory exec = openCommand(context, descriptor, 0);
@@ -48,22 +114,24 @@ abstract contract Repay is CommandBase, RepayHook, Action {
48
114
  }
49
115
  }
50
116
 
51
- /// @title RepayPayable
117
+ /// @title RepayPositionPayable
52
118
  /// @notice Funded command that repays POSITION liabilities and returns their assets as BALANCE state.
53
- abstract contract RepayPayable is CommandBase, RepayPayableHook, Action {
119
+ abstract contract RepayPositionPayable is CommandBase, RepayPayableHook, Action {
54
120
  uint private immutable descriptor;
55
121
 
56
122
  constructor() {
57
123
  uint id;
58
- (id, descriptor) = command("repayPayable", Specs.Position, Specs.Empty, Specs.Balance, 0, Flags.Funded);
59
- action(id, Actions.Settle);
124
+ (id, descriptor) = command(
125
+ "repayPositionPayable", Specs.Position, Specs.Empty, Specs.Balance, 0, Flags.Funded
126
+ );
127
+ action(id, Actions.Repay);
60
128
  }
61
129
 
62
130
  /// @notice Repay each POSITION liability and return its asset as BALANCE state.
63
131
  /// @param context Command context carrying the POSITION state stream.
64
132
  /// @return BALANCE output state containing each released asset side.
65
133
  /// @return Remaining native value as a refund transaction stream.
66
- function repayPayable(
134
+ function repayPositionPayable(
67
135
  bytes calldata context
68
136
  ) external payable onlyCommand returns (bytes memory, bytes memory) {
69
137
  Execution memory exec = openCommand(context, descriptor, 0);
@@ -77,3 +145,35 @@ abstract contract RepayPayable is CommandBase, RepayPayableHook, Action {
77
145
  return closeCommand(exec);
78
146
  }
79
147
  }
148
+
149
+ /// @title RepayInternal
150
+ /// @notice Extends the advertised repay command with memory-state pipeline dispatch.
151
+ /// @dev This adapter is not a separate command. It uses the command ID and repayment hook
152
+ /// inherited from `Repay` while accepting the state location used by `Pipeline`.
153
+ abstract contract RepayInternal is Repay {
154
+ /// @notice Execute the inherited repay command from an internal pipeline.
155
+ /// @param account Account whose liabilities are repaid.
156
+ /// @param state DEBT block stream held in pipeline memory.
157
+ /// @param input Empty input required by the command schema.
158
+ /// @param value Native value assigned to the command; must be zero.
159
+ /// @return output Empty output state.
160
+ /// @return transactions Empty transaction stream.
161
+ function executeRepay(
162
+ bytes32 account,
163
+ bytes memory state,
164
+ bytes calldata input,
165
+ uint128 value
166
+ ) internal returns (bytes memory, bytes memory) {
167
+ if (value != 0) revert ValueNotAllowed();
168
+ if (input.length != 0) revert Executions.ZeroStride();
169
+ if (state.length == 0) revert Blocks.EmptyRun();
170
+
171
+ Reader memory reader = Readers.open(state);
172
+ while (reader.more()) {
173
+ (bytes32 liability, uint debt) = reader.unpackDebt();
174
+ repay(account, liability, debt);
175
+ }
176
+
177
+ return ("", "");
178
+ }
179
+ }
package/core/Access.sol CHANGED
@@ -12,6 +12,14 @@ import {addrOr} from "../utils/Utils.sol";
12
12
  error AccessDenied();
13
13
  error CommanderNotAllowed();
14
14
 
15
+ /// @notice Assert that `msg.sender` equals `expected` and return it.
16
+ /// @param expected Address required to be the current message sender.
17
+ /// @return The validated sender address.
18
+ function enforceSender(address expected) view returns (address) {
19
+ if (msg.sender != expected) revert AccessDenied();
20
+ return expected;
21
+ }
22
+
15
23
  /// @title CallerAccess
16
24
  /// @notice Authorization capability required by command entrypoints.
17
25
  abstract contract CallerAccess is Runtime {
package/core/Types.sol CHANGED
@@ -67,6 +67,14 @@ struct HostAccountAmount {
67
67
  uint amount;
68
68
  }
69
69
 
70
+ /// @notice Liability and debt pair threaded as live pipeline state.
71
+ struct Debt {
72
+ /// @dev Identifier for the liability side.
73
+ bytes32 liability;
74
+ /// @dev Quantity owed on the liability side.
75
+ uint debt;
76
+ }
77
+
70
78
  /// @notice Asset and liability pair threaded as live pipeline state.
71
79
  struct Position {
72
80
  /// @dev Identifier for the asset side.
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-only
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import {AssetAmount, AccountAsset, HostAsset, AccountAmount, HostAmount, HostAccountAsset, Position, Tx} from "../core/Types.sol";
4
+ import {AssetAmount, AccountAsset, HostAsset, AccountAmount, HostAmount, HostAccountAsset, Debt, Position, Tx} from "../core/Types.sol";
5
5
  import {Blocks} from "../codec/Blocks.sol";
6
6
  import {Buffers} from "../codec/Buffers.sol";
7
7
  import {Sizes, Specs} from "../codec/Specs.sol";
@@ -528,6 +528,18 @@ library Executions {
528
528
  (value.asset, value.amount) = unpackBalance(exec, lane);
529
529
  }
530
530
 
531
+ /// @notice Decode and consume one DEBT block from `lane`.
532
+ function unpackDebt(Execution memory exec, uint8 lane) internal pure returns (bytes32 liability, uint debt) {
533
+ uint abs;
534
+ (exec.decoders, abs) = exec.decoders.consume(lane, Sizes.Debt);
535
+ (liability, debt) = Blocks.unpackDebt(abs);
536
+ }
537
+
538
+ /// @notice Decode one DEBT block into its structured value.
539
+ function unpackDebtValue(Execution memory exec, uint8 lane) internal pure returns (Debt memory value) {
540
+ (value.liability, value.debt) = unpackDebt(exec, lane);
541
+ }
542
+
531
543
  /// @notice Decode and consume one POSITION block from `lane`.
532
544
  function unpackPosition(
533
545
  Execution memory exec,
@@ -990,6 +1002,17 @@ library Executions {
990
1002
  outputBalance(exec, value.asset, value.amount);
991
1003
  }
992
1004
 
1005
+ /// @notice Append a DEBT block to execution output.
1006
+ function outputDebt(Execution memory exec, bytes32 liability, uint debt) internal pure {
1007
+ uint i = reserve(exec, Sizes.Debt);
1008
+ Blocks.writeDebt(exec.output, i, liability, debt);
1009
+ }
1010
+
1011
+ /// @notice Append a structured DEBT value to execution output.
1012
+ function outputDebt(Execution memory exec, Debt memory value) internal pure {
1013
+ outputDebt(exec, value.liability, value.debt);
1014
+ }
1015
+
993
1016
  /// @notice Append a POSITION block to execution output.
994
1017
  function outputPosition(
995
1018
  Execution memory exec,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rootzero/contracts",
3
- "version": "1.23.0",
3
+ "version": "1.24.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/utils/Utils.sol CHANGED
@@ -10,6 +10,8 @@ error ValueOverflow();
10
10
  error NotDivisible();
11
11
  /// @dev Thrown when an ID claims to carry an address but the embedded address is zero.
12
12
  error ZeroAddress();
13
+ /// @dev Thrown when an address does not contain deployed bytecode.
14
+ error InvalidContract();
13
15
 
14
16
  /// @notice Assert that `value` fits in uint8 and return it as uint8.
15
17
  function max8(uint value) pure returns (uint8) {
@@ -146,6 +148,14 @@ function ensureAddr(address addr) pure returns (address) {
146
148
  return addr;
147
149
  }
148
150
 
151
+ /// @notice Assert that `target` contains deployed bytecode and return it unchanged.
152
+ /// @dev Rejects EOAs, zero and future deployment addresses, contracts currently
153
+ /// under construction, and precompiles whose code length is zero.
154
+ function ensureContract(address target) view returns (address) {
155
+ if (target.code.length == 0) revert InvalidContract();
156
+ return target;
157
+ }
158
+
149
159
  /// @notice Convert a signed integer to its 32-byte two's-complement representation.
150
160
  function intToBytes32(int value) pure returns (bytes32) {
151
161
  return bytes32(uint(value));