use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use miden_agglayer::create_bridge_account;
use miden_client::Deserializable;
use miden_client::account::{AccountFile, AccountId, AccountType};
use miden_client::auth::RPO_FALCON_SCHEME_ID;
use miden_client::crypto::FeltRng;
use miden_client::keystore::Keystore;
use miden_client::testing::common::{
FilesystemKeyStore,
TestClient,
insert_new_wallet,
wait_for_node,
wait_for_tx,
};
use miden_client::transaction::TransactionRequestBuilder;
use crate::tests::config::ClientConfig;
pub mod agglayer_bridge_in_out;
mod agglayer_test_utils;
pub mod ger;
pub const MIDEN_AGGLAYER_NETWORK_ID: u32 = 77;
pub struct AgglayerConfig {
pub bridge_admin: AccountFile,
pub ger_manager: AccountFile,
pub bridge: AccountFile,
pub faucet: AccountFile,
}
impl AgglayerConfig {
const BRIDGE_ADMIN_FILE: &str = "bridge_admin.mac";
const GER_MANAGER_FILE: &str = "ger_manager.mac";
const BRIDGE_FILE: &str = "bridge.mac";
const FAUCET_FILE: &str = "agglayer_faucet.mac";
pub fn from_env() -> Result<Option<Self>> {
match std::env::var("AGGLAYER_ACCOUNTS_DIR") {
Ok(dir) => {
let dir = PathBuf::from(dir);
let bridge_admin = Self::load_account_file(&dir, Self::BRIDGE_ADMIN_FILE)?;
let ger_manager = Self::load_account_file(&dir, Self::GER_MANAGER_FILE)?;
let bridge = Self::load_account_file(&dir, Self::BRIDGE_FILE)?;
let faucet = Self::load_account_file(&dir, Self::FAUCET_FILE)?;
Ok(Some(Self {
bridge_admin,
ger_manager,
bridge,
faucet,
}))
},
Err(_) => Ok(None),
}
}
pub fn bridge_admin_id(&self) -> AccountId {
self.bridge_admin.account.id()
}
pub fn ger_manager_id(&self) -> AccountId {
self.ger_manager.account.id()
}
pub fn bridge_id(&self) -> AccountId {
self.bridge.account.id()
}
pub fn faucet_id(&self) -> AccountId {
self.faucet.account.id()
}
pub async fn import_account(
&self,
account_id: AccountId,
client: &mut TestClient,
keystore: &FilesystemKeyStore,
) -> Result<()> {
let account_file = [&self.bridge_admin, &self.ger_manager, &self.bridge, &self.faucet]
.into_iter()
.find(|f| f.account.id() == account_id)
.with_context(|| format!("account {account_id} not found in agglayer config"))?;
client
.import_account_by_id(account_id)
.await
.with_context(|| format!("failed to import account {account_id} from network"))?;
for secret_key in &account_file.auth_secret_keys {
keystore.add_key(secret_key, account_id).await.with_context(|| {
format!("failed to add key for account {account_id} to keystore")
})?;
}
Ok(())
}
fn load_account_file(dir: &Path, filename: &str) -> Result<AccountFile> {
let path = dir.join(filename);
let bytes =
std::fs::read(&path).with_context(|| format!("failed to read {}", path.display()))?;
AccountFile::read_from_bytes(&bytes)
.map_err(|e| anyhow::anyhow!("failed to deserialize {}: {}", path.display(), e))
}
}
pub struct ClientPair {
pub client: TestClient,
pub keystore: FilesystemKeyStore,
}
pub type CoreAccountIds = (AccountId, AccountId, AccountId);
pub async fn create_agglayer_clients(
client_config: &ClientConfig,
) -> Result<(ClientPair, ClientPair, ClientPair)> {
let (mut client, keystore) = client_config.clone().into_client().await?;
wait_for_node(&mut client).await;
client.sync_state().await?;
println!("[setup] Bridge admin client initialized");
let bridge_admin = ClientPair { client, keystore };
let (client, keystore) = ClientConfig::default()
.with_rpc_endpoint(client_config.rpc_endpoint())
.into_client()
.await?;
println!("[setup] GER manager client initialized");
let ger_manager = ClientPair { client, keystore };
let (client, keystore) = ClientConfig::default()
.with_rpc_endpoint(client_config.rpc_endpoint())
.into_client()
.await?;
println!("[setup] User client initialized");
let user = ClientPair { client, keystore };
Ok((bridge_admin, ger_manager, user))
}
pub async fn setup_core_accounts(
config: Option<&AgglayerConfig>,
bridge_admin: &mut ClientPair,
ger_manager: &mut ClientPair,
user: &mut ClientPair,
) -> Result<CoreAccountIds> {
if let Some(config) = config {
println!("[setup] Loading core accounts from genesis");
println!("[setup] bridge admin: {}", config.bridge_admin_id());
println!("[setup] GER manager: {}", config.ger_manager_id());
println!("[setup] bridge: {}", config.bridge_id());
config
.import_account(
config.bridge_admin_id(),
&mut bridge_admin.client,
&bridge_admin.keystore,
)
.await?;
config
.import_account(config.ger_manager_id(), &mut ger_manager.client, &ger_manager.keystore)
.await?;
for pair in [&mut *bridge_admin, &mut *ger_manager, &mut *user] {
config
.import_account(config.bridge_id(), &mut pair.client, &pair.keystore)
.await?;
}
return Ok((config.bridge_admin_id(), config.ger_manager_id(), config.bridge_id()));
}
println!("[setup] Creating core accounts at runtime");
let (bridge_admin_account, ..) = insert_new_wallet(
&mut bridge_admin.client,
AccountType::Public,
&bridge_admin.keystore,
RPO_FALCON_SCHEME_ID,
)
.await?;
let (ger_manager_account, ..) = insert_new_wallet(
&mut ger_manager.client,
AccountType::Public,
&ger_manager.keystore,
RPO_FALCON_SCHEME_ID,
)
.await?;
let bridge_seed = bridge_admin.client.rng().draw_word();
let bridge_account = create_bridge_account(
bridge_seed,
bridge_admin_account.id(),
ger_manager_account.id(),
MIDEN_AGGLAYER_NETWORK_ID,
);
println!("[setup] bridge admin: {}", bridge_admin_account.id());
println!("[setup] GER manager: {}", ger_manager_account.id());
println!("[setup] bridge: {}", bridge_account.id());
for pair in [&mut *bridge_admin, &mut *ger_manager, &mut *user] {
pair.client.add_account(&bridge_account, false).await?;
}
let deploy_tx = TransactionRequestBuilder::new().build()?;
let tx_id = bridge_admin
.client
.submit_new_transaction(bridge_account.id(), deploy_tx)
.await?;
wait_for_tx(&mut bridge_admin.client, tx_id).await?;
println!("[setup] Bridge account deployed on-chain");
Ok((bridge_admin_account.id(), ger_manager_account.id(), bridge_account.id()))
}