Skip to main content

blueprint_eigenlayer_extra/services/
slashing.rs

1use crate::error::{EigenlayerExtraError, Result};
2use alloy_primitives::Address;
3use blueprint_core::info;
4use blueprint_keystore::backends::Backend;
5use blueprint_keystore::backends::ecdsa::EcdsaBackend;
6use blueprint_keystore::crypto::k256::K256Ecdsa;
7use blueprint_runner::config::BlueprintEnvironment;
8use eigensdk::utils::slashing::core::delegation_manager::DelegationManager;
9
10/// Status information about operator slashing
11#[derive(Debug, Clone)]
12pub struct SlashingStatus {
13    /// Whether the operator is currently slashed
14    pub is_slashed: bool,
15    /// Operator address
16    pub operator_address: Address,
17}
18
19/// Event emitted when slashing is detected
20#[derive(Debug, Clone)]
21pub struct SlashingEvent {
22    /// Operator address that was slashed
23    pub operator_address: Address,
24    /// Strategy address where slashing occurred
25    pub strategy_address: Address,
26    /// Amount slashed
27    pub amount: alloy_primitives::U256,
28    /// Block number where slashing occurred
29    pub block_number: u64,
30}
31
32/// Monitor for operator slashing events
33///
34/// Provides event-driven slashing detection and status querying for EigenLayer operators.
35/// Integrates with the DelegationManager contract to track slashing events.
36#[derive(Clone)]
37pub struct SlashingMonitor {
38    env: BlueprintEnvironment,
39}
40
41impl SlashingMonitor {
42    /// Create a new SlashingMonitor
43    pub fn new(env: BlueprintEnvironment) -> Self {
44        Self { env }
45    }
46
47    /// Get the operator address from keystore
48    ///
49    /// # Errors
50    ///
51    /// * Keystore errors if ECDSA key not found or cannot be exposed
52    fn get_operator_address(&self) -> Result<Address> {
53        let ecdsa_public = self
54            .env
55            .keystore()
56            .first_local::<K256Ecdsa>()
57            .map_err(EigenlayerExtraError::Keystore)?;
58
59        let ecdsa_secret = self
60            .env
61            .keystore()
62            .expose_ecdsa_secret(&ecdsa_public)
63            .map_err(EigenlayerExtraError::Keystore)?
64            .ok_or_else(|| {
65                EigenlayerExtraError::InvalidConfiguration("No ECDSA secret found".into())
66            })?;
67
68        ecdsa_secret
69            .alloy_address()
70            .map_err(|e| EigenlayerExtraError::InvalidConfiguration(e.to_string()))
71    }
72
73    /// Check if the operator is slashed
74    ///
75    /// # Errors
76    ///
77    /// * Contract interaction errors
78    /// * Configuration errors
79    pub async fn is_operator_slashed(&self) -> Result<bool> {
80        let operator_address = self.get_operator_address()?;
81        let contract_addresses = self
82            .env
83            .protocol_settings
84            .eigenlayer()
85            .map_err(|e| EigenlayerExtraError::InvalidConfiguration(e.to_string()))?;
86
87        let provider =
88            blueprint_evm_extra::util::get_provider_http(self.env.http_rpc_endpoint.clone());
89
90        let delegation_manager = DelegationManager::DelegationManagerInstance::new(
91            contract_addresses.delegation_manager_address,
92            provider,
93        );
94
95        // Check if operator is registered (slashed operators are typically deregistered)
96        let is_operator = delegation_manager
97            .isOperator(operator_address)
98            .call()
99            .await
100            .map_err(|e| EigenlayerExtraError::Contract(e.to_string()))?;
101
102        // If not an operator, might have been slashed and deregistered
103        Ok(!is_operator)
104    }
105
106    /// Get detailed slashing status for the operator
107    ///
108    /// # Errors
109    ///
110    /// * Contract interaction errors
111    /// * Configuration errors
112    pub async fn get_slashing_status(&self) -> Result<SlashingStatus> {
113        let operator_address = self.get_operator_address()?;
114        let is_slashed = self.is_operator_slashed().await?;
115
116        Ok(SlashingStatus {
117            is_slashed,
118            operator_address,
119        })
120    }
121
122    /// Query slashable shares for the operator across strategies
123    ///
124    /// Returns the amount of shares that can be slashed for a given strategy.
125    ///
126    /// # Arguments
127    ///
128    /// * `strategy_address` - The strategy address to query
129    ///
130    /// # Errors
131    ///
132    /// * Contract interaction errors
133    /// * Configuration errors
134    pub async fn get_slashable_shares(
135        &self,
136        strategy_address: Address,
137    ) -> Result<alloy_primitives::U256> {
138        let operator_address = self.get_operator_address()?;
139        let contract_addresses = self
140            .env
141            .protocol_settings
142            .eigenlayer()
143            .map_err(|e| EigenlayerExtraError::InvalidConfiguration(e.to_string()))?;
144
145        let provider =
146            blueprint_evm_extra::util::get_provider_http(self.env.http_rpc_endpoint.clone());
147
148        let delegation_manager = DelegationManager::DelegationManagerInstance::new(
149            contract_addresses.delegation_manager_address,
150            provider,
151        );
152
153        let result = delegation_manager
154            .getSlashableSharesInQueue(operator_address, strategy_address)
155            .call()
156            .await
157            .map_err(|e| EigenlayerExtraError::Contract(e.to_string()))?;
158
159        info!(
160            "Slashable shares for operator {} in strategy {}: {}",
161            operator_address, strategy_address, result
162        );
163
164        Ok(result)
165    }
166}
167
168#[cfg(test)]
169mod tests {
170
171    #[tokio::test]
172    #[ignore] // Requires EigenLayer deployment
173    async fn test_slashing_monitor_creation() {
174        // This test would require a full BlueprintEnvironment setup
175        // with EigenLayer contract addresses
176    }
177}