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 lane paying this operator
350 /// survive untouched — all of them key on the Ethereum address,
351 /// which does 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;