Skip to main content

decdn_incentive/
capacity_bond.rs

1//! `CapacityBond` contract ABI bindings (ADR 003 § Node Registry, ADR 019).
2//!
3//! Generated by the `alloy::sol!` macro from the Solidity surface in
4//! `contracts/src/CapacityBond.sol`. The read paths the node runtime
5//! consumes plus the operator-onboarding write paths — `bond` /
6//! `declareMbps` (`decdn node bond`, ADR 019 § Step 2.1–2.2) and
7//! `registerNode` with its nonce getters (`decdn node register`, § Step
8//! 2.3) — are mirrored. See the macro body below for the exact surface.
9//! Two gotchas worth flagging here rather than at the call site:
10//!
11//! - `getRegisteredNodes` returns the `_registeredAddrs` page (every
12//!   registered operator, unfiltered for bond / unbonding / ejection)
13//!   alongside a parallel `active[]` that the contract computes with
14//!   `isActive(operator)` per entry — the strict "registered AND bond ≥
15//!   minBond AND no unbonding AND not ejected" predicate. Callers take
16//!   the membership set from `active[]` and keep the full page for the
17//!   payout bindings (an unbonding operator is `active == false` but
18//!   still payable), with no per-entry round-trip.
19//! - `nodeIdOf` returns `(bytes32 nodeId, bool active)` where the bool
20//!   is the full `isActive` predicate, not a separate "registered"
21//!   flag.
22
23// The `sol!`-generated bindings include macro-emitted code that uses
24// patterns workspace clippy denies (raw indexing into ABI fixed-size
25// byte arrays, `unwrap` on infallible conversions). These allows scope
26// the relaxation to this module only.
27#[allow(
28    clippy::expect_used,
29    clippy::indexing_slicing,
30    clippy::panic,
31    clippy::unwrap_used,
32    clippy::pub_underscore_fields,
33    clippy::missing_docs_in_private_items,
34    clippy::too_many_arguments,
35    missing_debug_implementations,
36    missing_docs,
37    non_snake_case,
38    non_camel_case_types
39)]
40mod sol_types {
41    alloy::sol! {
42        #[sol(rpc)]
43        contract CapacityBond {
44            // -----------------------------------------------------------------
45            // Structs
46            // -----------------------------------------------------------------
47
48            /// Per-operator registration record. Mirrors the on-chain
49            /// struct at `contracts/src/CapacityBond.sol`. Field
50            /// order is part of the ABI; do not rearrange.
51            struct NodeInfo {
52                bytes32 nodeId;
53                address ethAddress;
54                bool active;
55                uint64 lastMultiaddrUpdate;
56                bytes multiaddrs;
57                string regionHint;
58            }
59
60            // -----------------------------------------------------------------
61            // View functions (only those consumed by the node runtime)
62            // -----------------------------------------------------------------
63
64            /// Full active-bonder predicate: registered AND
65            /// `activeBond ≥ minBond` AND no unbonding request in
66            /// flight AND not ejected.
67            function isActive(address operator) external view returns (bool);
68
69            /// `nodeId`-keyed convenience over `isActive`.
70            function isActiveNode(bytes32 nodeId) external view returns (bool);
71
72            /// Operator → `(nodeId, isActive)`. `nodeId` is
73            /// `bytes32(0)` if the operator has no binding.
74            function nodeIdOf(address operator) external view returns (bytes32 nodeId, bool active);
75
76            /// Raw `nodeId → operator` binding. `address(0)` when the nodeId is
77            /// unbound. Read by `decdn node rotate-key` to pre-check the
78            /// `NodeIdAlreadyBound` revert before spending gas — `nodeIdOf`
79            /// answers the opposite direction and cannot see a collision with a
80            /// *different* operator's binding.
81            function nodeIdToAddress(bytes32 nodeId) external view returns (address);
82
83            /// Raw `operator → nodeId` binding, the mapping slashability keys
84            /// on: `SlashJudge._checkRegistered` reads it through `nodeIdOf` and
85            /// deliberately ignores `active`, so a bound-but-deregistered
86            /// operator stays slashable (ADR 014). `bindNodeId` swaps it
87            /// atomically, which is why key rotation opens no un-slashable
88            /// window. `bytes32(0)` when the operator has no binding.
89            function addressToNodeId(address operator) external view returns (bytes32);
90
91            /// Unix timestamp of the operator's first bond, or `0` if never
92            /// bonded. Write-once — never cleared by `deregisterNode`,
93            /// auto-ejection, or `bindNodeId`. Read by `decdn node rotate-key`
94            /// to report the `age_ramp` anchor an Ethereum-key rotation
95            /// destroys and an iroh-key rotation preserves (ADR 026
96            /// § Governance).
97            function firstBondedAt(address operator) external view returns (uint64);
98
99            /// On-chain escrow record for one slash (ADR 028 escrow-on-slash).
100            /// Field order is ABI-significant — it mirrors `SlashEscrowLib`'s
101            /// struct exactly, and a reordering here silently mis-decodes.
102            struct SlashRecord {
103                address operator;
104                uint64 slashedAt;
105                uint8 status;
106                uint8 offenseType;
107                address challenger;
108                uint64 appealWindowClose;
109                uint256 slashAmount;
110                bytes32 evidenceHash;
111            }
112
113            /// A slash record was minted. Indexed on `operator`, so a node
114            /// filters its own with `topic2` and then point-reads the record —
115            /// the live-tail counterpart to the `operatorSlash*` enumeration.
116            event SlashRecorded(
117                uint256 indexed slashId, address indexed operator, uint64 slashedAt, uint256 slashAmount
118            );
119
120            /// Paginated snapshot of `_registeredAddrs` — every operator
121            /// with a current registration, **NOT** filtered for bond /
122            /// unbonding / ejection — paired with a parallel `active[]`
123            /// where `active[i] == isActive(page[i].ethAddress)`, computed
124            /// on-chain. Take membership from `active`; keep `page` for the
125            /// payout bindings (an unbonding operator is `active == false`
126            /// but still present and payable).
127            function getRegisteredNodes(uint256 offset, uint256 limit)
128                external
129                view
130                returns (NodeInfo[] memory page, bool[] memory active);
131
132            /// Cardinality of `_registeredAddrs`.
133            function getRegisteredNodeCount() external view returns (uint256);
134
135            /// How many slashes have ever been minted against `operator`.
136            /// Paired with `operatorSlashIdAt`, this lets a consumer avoid re-scanning
137            /// the `Slashed` log tail from a block floor on every start: walk
138            /// backwards from `count - 1` and stop at the first record whose
139            /// `appealWindowClose` has already passed.
140            function operatorSlashCount(address operator) external view returns (uint256);
141
142            /// The `slashId` at `index` in `operator`'s append-only slash list.
143            /// Append-only and never reordered, so indices are stable across
144            /// calls and no pinned-block read is required. Reverts
145            /// `SlashIndexOutOfRange` past the end rather than returning a zero
146            /// id, which would alias the legitimate `slashId == 0`.
147            function operatorSlashIdAt(address operator, uint256 index) external view returns (uint256);
148
149            /// Full escrow record for `slashId`.
150            ///
151            /// `appealWindowClose` here is the **base** deadline (`slashedAt +
152            /// appealFilingWindow`), fixed at mint. The deadline the contract
153            /// actually enforces is `appealWindowClose + pausedTotal`
154            /// ([`SlashEscrowLib`] adds the global pause offset at finalize /
155            /// appeal-open time), so a consumer that gates on the deadline MUST
156            /// read [`pausedTotal`](Self::pausedTotal) at the same block and add
157            /// it. `offenseType` and `evidenceHash` are persisted on the record
158            /// precisely so it is self-sufficient — the `CapacityBond.Slashed`
159            /// event carries the offense but no `slashId`, and `SlashRecorded`
160            /// carries the `slashId` but no offense.
161            function getSlashRecord(uint256 slashId) external view returns (SlashRecord memory);
162
163            /// Global cumulative appeal-window pause offset, in seconds. Every
164            /// slash's enforced deadline is
165            /// `getSlashRecord(id).appealWindowClose + pausedTotal`; the offset
166            /// only ever grows (`_unpause` credits each ended pause interval), so
167            /// it uniformly extends every open window and preserves base-close
168            /// ordering.
169            function pausedTotal() external view returns (uint64);
170
171            /// The operator's raw registration record. `active` here is
172            /// `_nodes[operator].active` alone — the flag `deregisterNode`
173            /// gates on — NOT the composite `isActive`, which additionally
174            /// requires bond over `minBond` and no unbonding request. An
175            /// operator mid-unbonding is `isActive == false` yet still
176            /// deregisterable, so `decdn node deregister` reads this (#1359).
177            /// Zero-valued for a never-registered address.
178            function getNodeByAddress(address ethAddress) external view returns (NodeInfo memory);
179
180            /// Whether the operator was removed by the contract rather than by
181            /// choice — auto-ejection on slashing, or a `BLACKLIST_ROLE`
182            /// `ejectNode`. Both inactive states exit the same way
183            /// (`declareMbps(0)` + unbond), but they differ on RE-ENTRY: an
184            /// ejected operator's `registerNode` reverts `OperatorEjected` until
185            /// the latch clears, so the advice has to differ (#1359).
186            function ejected(address operator) external view returns (bool);
187
188            // -----------------------------------------------------------------
189            // Bonding views + writes (ADR 019 § Step 2.1–2.2, consumed by
190            // `decdn node bond`)
191            // -----------------------------------------------------------------
192
193            /// The bonded ERC-20 (TOKEN). Read so `decdn node bond` can build
194            /// the `approve` call without a separate config field.
195            function token() external view returns (address);
196
197            /// Active bond posted by `operator` (base units). Top-up target
198            /// math reads this to bond only the shortfall.
199            function activeBond(address operator) external view returns (uint256);
200
201            /// Global minimum bond floor (base units) enforced by
202            /// `registerNode`, independent of the capacity curve.
203            function minBond() external view returns (uint256);
204
205            /// Governable lower bound of the declarable-capacity band (Mbps).
206            /// Read so `decdn node bond` can reject an out-of-band tier before
207            /// bonding, rather than stranding a bond behind a `declareMbps`
208            /// revert.
209            function minCapacityMbps() external view returns (uint256);
210
211            /// Governable upper bound of the declarable-capacity band (Mbps).
212            function maxCapacityMbps() external view returns (uint256);
213
214            /// Operator's currently declared capacity tier (Mbps). Read so a
215            /// re-run can skip a redundant `declareMbps`.
216            function declaredMbps(address operator) external view returns (uint256);
217
218            /// Active bond required to declare `mbps` of capacity under the
219            /// current curve (`k × mbps^α`). `decdn node bond` sizes the
220            /// deposit from this rather than recomputing the curve off-chain.
221            function bondRequired(uint256 mbps) external view returns (uint256);
222
223            /// Deposit `amount` TOKEN into the operator's active bond
224            /// (additive). Requires a prior ERC-20 `approve`.
225            function bond(uint256 amount) external;
226
227            /// Self-attest serving capacity `mbps`. Reverts if `activeBond <
228            /// bondRequired(mbps)` or `mbps` is outside the governable band.
229            function declareMbps(uint256 mbps) external;
230
231            /// Start the unbonding window for `amount` base units (ADR 026
232            /// § Capacity-bond curve — lowering a bond is `requestUnbond` +
233            /// `unbond`, there is no atomic refund). Moves `amount` out of
234            /// `activeBond` immediately and locks it for `unbondingPeriod`.
235            /// Reverts if a request is already in flight, or if the remaining
236            /// active bond would fall below `bondRequired(declaredMbps)` — note
237            /// the curve, NOT `minBond`, is the floor here, so the operator can
238            /// legally unbond into an inactive state. `decdn node unbond`
239            /// pre-checks both so neither surfaces as a raw revert.
240            function requestUnbond(uint256 amount) external;
241
242            /// Withdraw a matured unbonding request (`block.timestamp >=
243            /// unlockAt`), transferring the TOKEN back to the operator and
244            /// clearing the request.
245            function unbond() external;
246
247            /// Operator's in-flight unbonding request as `(amount, unlockAt)`;
248            /// `amount == 0` means none. The auto-getter of a
249            /// `UnbondingRequest` mapping, so the static struct flattens to
250            /// this tuple. (Unlike `nodeIdOf` above, which is a hand-written
251            /// multi-return view rather than a struct getter.) A
252            /// non-zero `amount` also makes `isActive` false for the whole
253            /// window, which is why `decdn node unbond` warns before starting
254            /// one.
255            function unbondingOf(address operator) external view returns (uint256 amount, uint256 unlockAt);
256
257            /// Governable unbonding window in seconds (ADR 026 § Capacity-bond
258            /// curve — 14 days by default). Read so the CLI can report the
259            /// unlock time it is about to commit the operator to.
260            function unbondingPeriod() external view returns (uint256);
261
262            /// Governable ceiling (bytes) on the packed `multiaddrs` field an
263            /// operator may publish. `updateMultiaddrs` reverts
264            /// `MultiaddrsTooLarge` above it; `decdn node update-multiaddrs`
265            /// pre-checks against it so the cap surfaces before the send.
266            function maxMultiaddrSize() external view returns (uint256);
267
268            /// Governable minimum interval (seconds) between an operator's
269            /// `updateMultiaddrs` calls. `readyAt = lastMultiaddrUpdate +
270            /// multiaddrUpdateCooldown`; a call before it reverts
271            /// `MultiaddrCooldownActive(readyAt)`. Read so the CLI can report
272            /// the ready time rather than surface a bare revert.
273            function multiaddrUpdateCooldown() external view returns (uint256);
274
275            /// Region self-attestation state for `operator` (ADR 030), as
276            /// `(regionHint, regionPrev, regionLastChanged, regionStabilityWindow)`:
277            /// the current on-chain region, the previous region retained for the
278            /// blacklist-scope ripening predicate, the unix-second stamp of the
279            /// last change (registration counts), and the governable cooldown
280            /// between `updateRegion` calls. `decdn node update-region` reads it to
281            /// report the current region and the cooldown state — `readyAt =
282            /// regionLastChanged + regionStabilityWindow`; a call before it reverts
283            /// `RegionCooldownActive(readyAt)` — before the send.
284            function regionScopeData(address operator)
285                external
286                view
287                returns (
288                    string memory regionHint,
289                    string memory regionPrev,
290                    uint64 regionLastChanged,
291                    uint256 regionStabilityWindow
292                );
293
294            /// Per-operator EIP-712 `BindNodeId` nonce. Feeds the
295            /// `bindingSignature` digest at registration time (ADR 019
296            /// § Step 2.3). `0` for an operator that has never bound.
297            function bindingNonce(address operator) external view returns (uint64);
298
299            /// Per-nodeId registration nonce. Feeds the ed25519
300            /// ownership-proof digest (ADR 019 § Step 2.3); incremented on
301            /// deregistration / rebinding so a fresh signature is required on
302            /// re-onboarding. `0` for a never-registered nodeId.
303            function registrationNonce(bytes32 nodeId) external view returns (uint64);
304
305            /// Governance-canonical operator-terms hash new registrants must
306            /// accept (ADR 019 § Terms Acceptance). `registerNode` requires the
307            /// submitted `termsHash` to equal this value.
308            function currentTermsHash() external view returns (bytes32);
309
310            // -----------------------------------------------------------------
311            // Node registry write path (ADR 019 § Step 2.3)
312            // -----------------------------------------------------------------
313
314            /// Atomically bind `nodeId` ↔ `msg.sender` and activate the node.
315            /// Verifies the EIP-712 `bindingSignature` over
316            /// `RegisterNode(nodeId, bindingNonce[msg.sender], termsHash)` and
317            /// the ed25519 `ed25519Signature` over the ownership digest
318            /// (`decdn_incentive::node_register::ownership_message_digest`).
319            /// `termsHash` must equal `currentTermsHash` (ADR 019 § Terms
320            /// Acceptance). Reverts if the operator's bond is below `minBond` /
321            /// the declared-capacity curve — Phase 2.1/2.2 (`bond` /
322            /// `declareMbps`) are a precondition.
323            function registerNode(
324                bytes32 nodeId,
325                bytes multiaddrs,
326                string regionHint,
327                bytes32 termsHash,
328                bytes bindingSignature,
329                bytes ed25519Signature
330            ) external;
331
332            /// Leave the active set: clears `_nodes[msg.sender].active`, bumps
333            /// `registrationNonce[nodeId]`, and clears `declaredMbps` —
334            /// releasing the `bondRequired(declaredMbps)` floor that
335            /// `requestUnbond` enforces, which is what makes a full bond exit
336            /// reachable (ADR 003 § Node Registry, ADR 026 § Capacity-bond
337            /// curve). Does NOT move the bond. Reverts `NodeNotActive` unless
338            /// the caller is currently registered.
339            function deregisterNode() external;
340
341            /// Rebind the caller's operator address to `nodeId` — the iroh
342            /// node-key rotation path (`appendix-operator-key-rotation.md`
343            /// § iroh node-key rotation only), wrapped by `decdn node
344            /// rotate-key --key iroh`.
345            ///
346            /// Atomically deletes the old `nodeId → address` mapping (bumping
347            /// its `registrationNonce`) and writes the new one, so the operator
348            /// is never un-slashable mid-rotation. The bond, `declaredMbps`,
349            /// `firstBondedAt`, and every open payment channel survive
350            /// untouched — all of them key on the Ethereum address, which does
351            /// not change.
352            ///
353            /// `bindingSignature` covers `BindNodeId(nodeId,
354            /// bindingNonce[msg.sender])` — the terms-free typehash, NOT
355            /// `RegisterNode`: rebinding does not re-accept operator terms
356            /// (ADR 019 § enforcement at registration only). `ed25519Signature`
357            /// is the **new** node key's proof over
358            /// `node_register::ownership_message_digest`. Reverts
359            /// `NodeIdAlreadyBound` if a different operator holds `nodeId`,
360            /// `ZeroNodeId` on an all-zero id, and `InvalidBindingSignature` /
361            /// `InvalidEd25519Signature` on a mis-built digest.
362            function bindNodeId(bytes32 nodeId, bytes bindingSignature, bytes ed25519Signature) external;
363
364            /// (Re)publish the caller's packed `multiaddrs` (ADR 019 §
365            /// Multiaddr encoding — a sequence of `(uint16 length, bytes
366            /// data)` entries, the same `node_register::pack_multiaddrs`
367            /// framing `registerNode` takes). Wrapped by `decdn node
368            /// update-multiaddrs`, the only non-destructive way for an active
369            /// operator to promote direct QUIC addresses after registering an
370            /// empty set. Stores the bytes into `_nodes[msg.sender].multiaddrs`
371            /// and stamps `lastMultiaddrUpdate`. Reverts `NodeNotActive` unless
372            /// the caller is registered, `MultiaddrsTooLarge(size, ceiling)`
373            /// above `maxMultiaddrSize`, and `MultiaddrCooldownActive(readyAt)`
374            /// within `multiaddrUpdateCooldown` of the last update — all three
375            /// pre-checked by the CLI.
376            function updateMultiaddrs(bytes multiaddrs) external;
377
378            /// (Re)attest the caller's region (ADR 030), storing `newRegion` into
379            /// `_nodes[msg.sender].regionHint`, retaining the prior value in
380            /// `regionPrev`, and stamping `regionLastChanged`. Wrapped by `decdn
381            /// node update-region`, the non-destructive way for an active operator
382            /// to correct or move its region after registering. Reverts
383            /// `NodeNotActive` unless the caller is registered,
384            /// `RegionHintTooLong(size, ceiling)` above `MAX_REGION_HINT_BYTES`,
385            /// and `RegionCooldownActive(readyAt)` within `regionStabilityWindow`
386            /// of the last change — the active and cooldown gates pre-checked by the
387            /// CLI (a validated ISO 3166-1 alpha-2 code never nears the ceiling).
388            function updateRegion(string newRegion) external;
389
390            // -----------------------------------------------------------------
391            // Events that mutate active-set membership
392            // -----------------------------------------------------------------
393
394            /// Operator (re)published its dialable addresses via
395            /// `updateMultiaddrs`. `nodeId`-indexed; carries the new packed
396            /// `multiaddrs` field.
397            event NodeMultiaddrUpdated(bytes32 indexed nodeId, bytes multiaddrs);
398
399            /// Operator (re)attested its region via `updateRegion`.
400            /// `nodeId`-indexed; carries the prior and new region strings.
401            event RegionUpdated(bytes32 indexed nodeId, string oldRegion, string newRegion);
402
403            /// Node activated via `registerNode`. `nodeId`-indexed.
404            event NodeRegistered(
405                bytes32 indexed nodeId,
406                address indexed ethAddress,
407                bytes multiaddrs,
408                string regionHint,
409                uint64 bindingNonce,
410                uint64 registrationNonce
411            );
412
413            /// Node deactivated via `deregisterNode`. `nodeId`-indexed.
414            event NodeDeregistered(bytes32 indexed nodeId);
415
416            /// A `nodeId ↔ operator` binding was written, by either
417            /// `registerNode` or `bindNodeId`. The confirmation
418            /// `decdn node rotate-key` looks for in the bind receipt.
419            /// `bindingNonce` is the nonce the accepted signature consumed, not
420            /// the post-increment value.
421            event NodeIdBound(address indexed ethAddress, bytes32 indexed nodeId, uint64 bindingNonce);
422
423            /// Node deactivated by `slash` or `ejectNode` dropping
424            /// bond below `minBond / 2`. `nodeId`-indexed.
425            event NodeAutoEjected(bytes32 indexed nodeId, uint256 remainingBond);
426
427            /// Node deactivated by `ContentBlacklist`-role caller.
428            /// **Operator-indexed** — translate via `nodeIdOf`.
429            event EjectedByBlacklist(address indexed operator);
430
431            /// Previously ejected operator brought bond back to
432            /// `minBond` via `bond()`. **Operator-indexed** —
433            /// translate via `nodeIdOf`.
434            event Reinstated(address indexed operator);
435
436            /// Operator requested unbonding; flips `isActive` to
437            /// `false` immediately even though the operator stays in
438            /// `_registeredAddrs`. **Operator-indexed** — translate
439            /// via `nodeIdOf`.
440            event UnbondingRequested(
441                address indexed operator,
442                uint256 amount,
443                uint256 unlockAt,
444                uint256 newActiveBond
445            );
446        }
447    }
448}
449
450pub use sol_types::CapacityBond;