Skip to main content

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(
106            runtime_dir,
107            String::from("service"),
108            auth_proxy_db,
109            true,
110            None,
111        );
112        let bridge_socket_path = bridge.base_socket_path();
113
114        let (bridge_handle, _alive_rx) = bridge.spawn()?;
115
116        // Create context config
117        let context_config = ContextConfig::create_eigenlayer_config(
118            testnet.http_endpoint.clone(),
119            testnet.ws_endpoint.clone(),
120            keystore_path.to_string_lossy().into_owned(),
121            None,
122            data_dir,
123            None,
124            SupportedChains::LocalTestnet,
125            eigenlayer_protocol_settings,
126        );
127
128        // Load environment with bridge configuration
129        let mut env = BlueprintEnvironment::load_with_config(context_config)
130            .map_err(|e| Error::Setup(e.to_string()))?;
131
132        env.bridge_socket_path = Some(bridge_socket_path);
133        env.test_mode = true;
134
135        Ok(Self {
136            env,
137            _temp_dir: test_dir,
138            _container: testnet.container,
139            _auth_proxy: auth_proxy,
140            _bridge: bridge_handle,
141        })
142    }
143
144    #[must_use]
145    pub fn env(&self) -> &BlueprintEnvironment {
146        &self.env
147    }
148}
149
150/// Gets the accounts from the HTTP endpoint
151///
152/// # Panics
153///
154/// * See [`Provider::get_accounts()`]
155#[must_use]
156pub async fn get_accounts(http_endpoint: Url) -> Vec<Address> {
157    let provider = get_provider_http(http_endpoint.clone());
158    provider.get_accounts().await.unwrap()
159}
160
161/// Gets the owner account (first account)
162#[must_use]
163pub fn get_owner_account(accounts: &[Address]) -> Address {
164    accounts[0]
165}
166
167/// Gets the aggregator account (ninth account)
168#[must_use]
169pub fn get_aggregator_account(accounts: &[Address]) -> Address {
170    accounts[9]
171}
172
173/// Gets the task generator account (fourth account)
174#[must_use]
175pub fn get_task_generator_account(accounts: &[Address]) -> Address {
176    accounts[4]
177}
178
179/// Runs the authentication proxy server.
180///
181/// This function sets up and runs an authenticated proxy server that listens on the configured host and port.
182/// It creates necessary directories for the proxy's database and then starts the server.
183///
184/// # Arguments
185///
186/// * `data_dir` - The path to the data directory where the proxy's database will be stored.
187/// * `auth_proxy_port` - The port on which the proxy server will listen.
188///
189/// # Errors
190///
191/// This function will return an error if:
192/// - It fails to create the necessary directories for the database.
193/// - It fails to bind to the specified host and port.
194/// - The Axum server encounters an error during operation.
195async fn run_auth_proxy(
196    data_dir: PathBuf,
197    auth_proxy_port: u16,
198) -> Result<(RocksDb, impl Future<Output = Result<(), Error>>), Error> {
199    let db_path = data_dir.join("private").join("auth-proxy").join("db");
200    tokio::fs::create_dir_all(&db_path).await?;
201
202    let proxy = blueprint_auth::proxy::AuthenticatedProxy::new(&db_path)?;
203    let db = proxy.db();
204
205    let task = async move {
206        let router = proxy.router();
207        let listener =
208            tokio::net::TcpListener::bind((Ipv4Addr::LOCALHOST, auth_proxy_port)).await?;
209        info!(
210            "Auth proxy listening on {}:{}",
211            Ipv4Addr::LOCALHOST,
212            auth_proxy_port
213        );
214        let result = axum::serve(listener, router).await;
215        if let Err(err) = result {
216            error!("Auth proxy error: {err}");
217        }
218
219        Ok(())
220    };
221
222    Ok((db, task))
223}
224
225/// Deploy core Eigenlayer core contract
226///
227/// # Arguments
228///
229/// * `owner_private_key`: The private key of the owner account
230/// * `owner_account`: The owner account
231/// * `testnet`: The Anvil testnet
232///
233/// # Returns
234///
235/// * `Result<DeployedCoreContracts, Error>`: The deployed core contracts
236///     - `Ok(DeployedCoreContracts)`: The deployed core contracts
237///     - `Err(Error)`: The error
238///
239/// # Errors
240/// See [`eigenlayer_contract_deployer::core::deploy_core_contracts`]
241///
242/// # Panics
243/// See [`eigenlayer_contract_deployer::core::deploy_core_contracts`]
244pub async fn deploy_eigenlayer_core_contracts(
245    http_endpoint: &str,
246    owner_private_key: &str,
247    owner_account: Address,
248) -> Result<DeployedCoreContracts, Error> {
249    let core_config = DeploymentConfigData {
250        strategy_manager: StrategyManagerConfig {
251            init_paused_status: U256::from(0),
252            init_withdrawal_delay_blocks: 1u32,
253        },
254        delegation_manager: DelegationManagerConfig {
255            init_paused_status: U256::from(0),
256            withdrawal_delay_blocks: 0u32,
257        },
258        eigen_pod_manager: EigenPodManagerConfig {
259            init_paused_status: U256::from(0),
260        },
261        rewards_coordinator: RewardsCoordinatorConfig {
262            init_paused_status: U256::from(0),
263            max_rewards_duration: 864_000u32,
264            max_retroactive_length: 432_000u32,
265            max_future_length: 86_400u32,
266            genesis_rewards_timestamp: 1_672_531_200_u32,
267            updater: owner_account,
268            activation_delay: 0u32,
269            calculation_interval_seconds: 86_400u32,
270            global_operator_commission_bips: 1_000u16,
271        },
272        strategy_factory: StrategyFactoryConfig {
273            init_paused_status: U256::from(0),
274        },
275    };
276
277    Ok(eigenlayer_contract_deployer::core::deploy_core_contracts(
278        http_endpoint,
279        owner_private_key,
280        owner_account,
281        core_config,
282        Some(address!("00000000219ab540356cBB839Cbe05303d7705Fa")),
283        Some(1_564_000),
284    )
285    .await
286    .unwrap())
287}