dig_slashing/bonds.rs
1//! Bond-escrow surface: tag enum, error enum, and `BondEscrow` trait.
2//!
3//! Traces to: [SPEC.md §12.3](../docs/resources/SPEC.md), catalogue rows
4//! [DSL-121..126](../docs/requirements/domains/bonds/specs/).
5//!
6//! # Scope
7//!
8//! `dig-slashing` does NOT own escrow storage. The escrowed mojos live
9//! in `dig-collateral` (or a dedicated bond-escrow crate) that
10//! implements [`BondEscrow`]. This module defines the narrow trait
11//! surface the slashing manager + appeal adjudicator call through.
12//!
13//! # Symmetry
14//!
15//! Reporter and appellant bonds share the same trait + error surface.
16//! They are distinguished by the [`BondTag`] variant, which doubles as
17//! the unique escrow key — two concurrent bonds on the same principal
18//! cannot collide because the envelope/appeal hash is mixed in.
19
20use dig_protocol::Bytes32;
21use serde::{Deserialize, Serialize};
22use thiserror::Error;
23
24/// Bond categorisation + escrow key.
25///
26/// Traces to [SPEC §12.3](../../docs/resources/SPEC.md).
27///
28/// # Why the hash is part of the tag
29///
30/// `BondEscrow` uses the tag as a lookup key — `(principal_idx, tag)`
31/// is the uniquifier. Binding the evidence hash (resp. appeal hash)
32/// into the tag means the same validator can hold multiple
33/// concurrent bonds across independent evidences without collision.
34/// DSL-166 verifies `Reporter(h) != Appellant(h)` for any shared `h`.
35#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
36pub enum BondTag {
37 /// Reporter bond for the referenced `SlashingEvidence::hash()`.
38 /// Locked by DSL-023 in `SlashingManager::submit_evidence`.
39 Reporter(Bytes32),
40 /// Appellant bond for the referenced `SlashAppeal::hash()`.
41 /// Locked by DSL-062 in the appeal admission path.
42 Appellant(Bytes32),
43}
44
45/// Failure modes for `BondEscrow` operations.
46///
47/// Traces to [SPEC §17.3](../../docs/resources/SPEC.md). The variants
48/// are intentionally distinct so the slashing manager can attribute
49/// rejections correctly — `InsufficientBalance` → reporter lacks
50/// collateral; `DoubleLock` → state machine bug; `TagNotFound` →
51/// release/forfeit on an uninitialised tag.
52#[derive(Debug, Clone, Error, PartialEq, Eq)]
53pub enum BondError {
54 /// Principal's stake (net of outstanding slashes) is below `need`.
55 /// Raised by `lock`; surfaced at DSL-028 as `BondLockFailed`.
56 #[error("insufficient balance to lock bond: have {have}, need {need}")]
57 InsufficientBalance {
58 /// Available stake in mojos.
59 have: u64,
60 /// Amount requested.
61 need: u64,
62 },
63 /// `release` / `forfeit` called for a tag that was never locked.
64 #[error("bond tag {tag:?} not found")]
65 TagNotFound {
66 /// The offending tag.
67 tag: BondTag,
68 },
69 /// `lock` called for a tag already held — should never happen if
70 /// the manager's dedup (DSL-026) runs first.
71 #[error("bond tag {tag:?} already locked")]
72 DoubleLock {
73 /// The already-locked tag.
74 tag: BondTag,
75 },
76}
77
78/// Bond-escrow storage interface consumed by the slashing manager +
79/// appeal adjudicator.
80///
81/// Traces to [SPEC §12.3](../../docs/resources/SPEC.md). Concrete
82/// impls live in `dig-collateral` (or equivalent). Every method is
83/// `&mut self` except `escrowed` — mutating operations acquire
84/// exclusive access to the underlying coin store.
85pub trait BondEscrow {
86 /// Move `amount` mojos from the principal's free stake into
87 /// escrow under `tag`.
88 ///
89 /// - `Ok(())` on success.
90 /// - `Err(BondError::InsufficientBalance { .. })` if the
91 /// principal lacks collateral.
92 /// - `Err(BondError::DoubleLock { tag })` if the tag is already
93 /// locked (programmer bug — dedup should prevent this).
94 fn lock(&mut self, principal_idx: u32, amount: u64, tag: BondTag) -> Result<(), BondError>;
95 /// Release `amount` back to the principal's free stake. Called on
96 /// finalisation (DSL-031) or appeal-rejected/sustained unwinds.
97 fn release(&mut self, principal_idx: u32, amount: u64, tag: BondTag) -> Result<(), BondError>;
98 /// Forfeit `amount` from the escrow. Returns the forfeited mojos
99 /// so callers can route to winner-award + burn split (DSL-068,
100 /// DSL-071). On a sustained appeal (DSL-068) the forfeited mojos
101 /// come from the reporter; on a rejected appeal (DSL-071) they
102 /// come from the appellant.
103 fn forfeit(&mut self, principal_idx: u32, amount: u64, tag: BondTag) -> Result<u64, BondError>;
104 /// Currently-escrowed mojos under `(principal_idx, tag)`. `0`
105 /// when the tag is not present — read-only.
106 fn escrowed(&self, principal_idx: u32, tag: BondTag) -> u64;
107}