blueprint_eigenlayer_testing_utils/
harness.rs

1use crate::Error;
2use crate::env::setup_eigenlayer_test_environment;
3use alloy_primitives::address;
4use alloy_primitives::{Address, U256};
5use alloy_provider::Provider;
6use blueprint_auth::db::RocksDb;
7use blueprint_chain_setup::anvil::keys::inject_anvil_key;
8use blueprint_chain_setup::anvil::{AnvilTestnet, Container};
9use blueprint_core::{error, info};
10use blueprint_evm_extra::util::get_provider_http;
11use blueprint_manager_bridge::server::{Bridge, BridgeHandle};
12use blueprint_runner::config::{BlueprintEnvironment, ContextConfig, SupportedChains};
13use blueprint_runner::eigenlayer::config::EigenlayerProtocolSettings;
14use eigenlayer_contract_deployer::core::{
15    DelegationManagerConfig, DeployedCoreContracts, DeploymentConfigData, EigenPodManagerConfig,
16    RewardsCoordinatorConfig, StrategyFactoryConfig, StrategyManagerConfig,
17};
18use std::future::Future;
19use std::net::Ipv4Addr;
20use std::path::PathBuf;
21use tempfile::TempDir;
22use tokio::task::JoinHandle;
23use url::Url;
24
25/// Test harness for Eigenlayer network tests
26pub struct EigenlayerTestHarness {
27    env: BlueprintEnvironment,
28    _temp_dir: TempDir,
29    _container: Container,
30    _auth_proxy: JoinHandle<Result<(), Error>>,
31    _bridge: BridgeHandle,
32}
33
34impl EigenlayerTestHarness {
35    ///
36    /// Create a new `EigenlayerTestHarness`
37    ///
38    /// NOTE: The resulting harness will have a context of `()`. This is not valid for jobs that require
39    ///       a context. See [`Self::setup_with_context()`] and [`Self::set_context()`].
40    ///
41    /// # Errors
42    ///
43    /// * See [`EigenlayerTestHarness::setup_with_context()`]
44    pub async fn setup(
45        owner_private_key: &str,
46        test_dir: TempDir,
47        testnet: AnvilTestnet,
48        eigenlayer_protocol_settings: Option<EigenlayerProtocolSettings>,
49    ) -> Result<Self, Error> {
50        Self::setup_with_context(
51            owner_private_key,
52            test_dir,
53            testnet,
54            eigenlayer_protocol_settings,
55        )
56        .await
57    }
58}
59
60impl EigenlayerTestHarness {
61    /// Create a new `EigenlayerTestHarness` with a predefined context
62    ///
63    /// NOTE: If your context type depends on [`Self::env()`], see [`Self::setup()`]
64    ///
65    /// * Params
66    /// - `owner_private_key`: The private key of the owner account
67    /// - `test_dir`: The directory to store the test data
68    /// - `testnet`: The Anvil testnet
69    /// - `eigenlayer_protocol_settings`: The Eigenlayer protocol settings.
70    ///     - Option<T>: When you set up empty Anvil testnet and re-deploy smart contracts your-self
71    ///     - None: When you use the default Eigenlayer test environment
72    ///
73    /// # Errors
74    ///
75    /// * See [`crate::Error`]
76    pub async fn setup_with_context(
77        owner_private_key: &str,
78        test_dir: TempDir,
79        testnet: AnvilTestnet,
80        eigenlayer_protocol_settings: Option<EigenlayerProtocolSettings>,
81    ) -> Result<Self, Error> {
82        // Setup temporary testing keystore
83        let keystore_path = test_dir.path().join("keystore");
84        inject_anvil_key(&keystore_path, owner_private_key)?;
85
86        let eigenlayer_protocol_settings = match eigenlayer_protocol_settings {
87            Some(settings) => settings,
88            None => setup_eigenlayer_test_environment(testnet.http_endpoint.clone()).await,
89        };
90
91        let data_dir = test_dir.path().join("data");
92        tokio::fs::create_dir_all(&data_dir).await?;
93
94        // Setup auth proxy
95        const DEFAULT_AUTH_PROXY_PORT: u16 = 50051;
96        let (auth_proxy_db, auth_proxy_task) =
97            run_auth_proxy(test_dir.path().to_path_buf(), DEFAULT_AUTH_PROXY_PORT).await?;
98
99        let auth_proxy = tokio::spawn(auth_proxy_task);
100
101        // Setup bridge
102        let runtime_dir = test_dir.path().join("runtime");
103        tokio::fs::create_dir_all(&runtime_dir).await?;
104
105        let bridge = Bridge::new(runtime_dir, String::from("service"), auth_proxy_db, true);
106        let bridge_socket_path = bridge.base_socket_path();
107
108        let (bridge_handle, _alive_rx) = bridge.spawn()?;
109
110        // Create context config
111        let context_config = ContextConfig::create_eigenlayer_config(
112            testnet.http_endpoint.clone(),
113            testnet.ws_endpoint.clone(),
114            keystore_path.to_string_lossy().into_owned(),
115            None,
116            data_dir,
117            None,
118            SupportedChains::LocalTestnet,
119            eigenlayer_protocol_settings,
120        );
121
122        // Load environment with bridge configuration
123        let mut env = BlueprintEnvironment::load_with_config(context_config)
124            .map_err(|e| Error::Setup(e.to_string()))?;
125
126        env.bridge_socket_path = Some(bridge_socket_path);
127        env.test_mode = true;
128
129        Ok(Self {
130            env,
131            _temp_dir: test_dir,
132            _container: testnet.container,
133            _auth_proxy: auth_proxy,
134            _bridge: bridge_handle,
135        })
136    }
137
138    #[must_use]
139    pub fn env(&self) -> &BlueprintEnvironment {
140        &self.env
141    }
142}
143
144/// Gets the accounts from the HTTP endpoint
145///
146/// # Panics
147///
148/// * See [`Provider::get_accounts()`]
149#[must_use]
150pub async fn get_accounts(http_endpoint: Url) -> Vec<Address> {
151    let provider = get_provider_http(http_endpoint.clone());
152    provider.get_accounts().await.unwrap()
153}
154
155/// Gets the owner account (first account)
156#[must_use]
157pub fn get_owner_account(accounts: &[Address]) -> Address {
158    accounts[0]
159}
160
161/// Gets the aggregator account (ninth account)
162#[must_use]
163pub fn get_aggregator_account(accounts: &[Address]) -> Address {
164    accounts[9]
165}
166
167/// Gets the task generator account (fourth account)
168#[must_use]
169pub fn get_task_generator_account(accounts: &[Address]) -> Address {
170    accounts[4]
171}
172
173/// Runs the authentication proxy server.
174///
175/// This function sets up and runs an authenticated proxy server that listens on the configured host and port.
176/// It creates necessary directories for the proxy's database and then starts the server.
177///
178/// # Arguments
179///
180/// * `data_dir` - The path to the data directory where the proxy's database will be stored.
181/// * `auth_proxy_port` - The port on which the proxy server will listen.
182///
183/// # Errors
184///
185/// This function will return an error if:
186/// - It fails to create the necessary directories for the database.
187/// - It fails to bind to the specified host and port.
188/// - The Axum server encounters an error during operation.
189async fn run_auth_proxy(
190    data_dir: PathBuf,
191    auth_proxy_port: u16,
192) -> Result<(RocksDb, impl Future<Output = Result<(), Error>>), Error> {
193    let db_path = data_dir.join("private").join("auth-proxy").join("db");
194    tokio::fs::create_dir_all(&db_path).await?;
195
196    let proxy = blueprint_auth::proxy::AuthenticatedProxy::new(&db_path)?;
197    let db = proxy.db();
198
199    let task = async move {
200        let router = proxy.router();
201        let listener =
202            tokio::net::TcpListener::bind((Ipv4Addr::LOCALHOST, auth_proxy_port)).await?;
203        info!(
204            "Auth proxy listening on {}:{}",
205            Ipv4Addr::LOCALHOST,
206            auth_proxy_port
207        );
208        let result = axum::serve(listener, router).await;
209        if let Err(err) = result {
210            error!("Auth proxy error: {err}");
211        }
212
213        Ok(())
214    };
215
216    Ok((db, task))
217}
218
219/// Deploy core Eigenlayer core contract
220///
221/// # Arguments
222///
223/// * `owner_private_key`: The private key of the owner account
224/// * `owner_account`: The owner account
225/// * `testnet`: The Anvil testnet
226///
227/// # Returns
228///
229/// * `Result<DeployedCoreContracts, Error>`: The deployed core contracts
230///     - `Ok(DeployedCoreContracts)`: The deployed core contracts
231///     - `Err(Error)`: The error
232///
233/// # Errors
234/// See [`eigenlayer_contract_deployer::core::deploy_core_contracts`]
235///
236/// # Panics
237/// See [`eigenlayer_contract_deployer::core::deploy_core_contracts`]
238pub async fn deploy_eigenlayer_core_contracts(
239    http_endpoint: &str,
240    owner_private_key: &str,
241    owner_account: Address,
242) -> Result<DeployedCoreContracts, Error> {
243    let core_config = DeploymentConfigData {
244        strategy_manager: StrategyManagerConfig {
245            init_paused_status: U256::from(0),
246            init_withdrawal_delay_blocks: 1u32,
247        },
248        delegation_manager: DelegationManagerConfig {
249            init_paused_status: U256::from(0),
250            withdrawal_delay_blocks: 0u32,
251        },
252        eigen_pod_manager: EigenPodManagerConfig {
253            init_paused_status: U256::from(0),
254        },
255        rewards_coordinator: RewardsCoordinatorConfig {
256            init_paused_status: U256::from(0),
257            max_rewards_duration: 864_000u32,
258            max_retroactive_length: 432_000u32,
259            max_future_length: 86_400u32,
260            genesis_rewards_timestamp: 1_672_531_200_u32,
261            updater: owner_account,
262            activation_delay: 0u32,
263            calculation_interval_seconds: 86_400u32,
264            global_operator_commission_bips: 1_000u16,
265        },
266        strategy_factory: StrategyFactoryConfig {
267            init_paused_status: U256::from(0),
268        },
269    };
270
271    Ok(eigenlayer_contract_deployer::core::deploy_core_contracts(
272        http_endpoint,
273        owner_private_key,
274        owner_account,
275        core_config,
276        Some(address!("00000000219ab540356cBB839Cbe05303d7705Fa")),
277        Some(1_564_000),
278    )
279    .await
280    .unwrap())
281}