Skip to main content

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}