Skip to content

Commit 7842424

Browse files
committed
Refactor CLI and core modules with layer-zero support
1 parent 8f7c03c commit 7842424

11 files changed

Lines changed: 248 additions & 131 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cli/src/cli.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
use clap::{Parser, Subcommand};
2+
3+
#[derive(Parser)]
4+
#[command(name = "deploy-cli")]
5+
#[command(about = "Deploy contracts and manage deployer", long_about = None)]
6+
pub struct Cli {
7+
#[arg(long)]
8+
pub chain: String,
9+
10+
#[command(subcommand)]
11+
pub command: Commands,
12+
}
13+
#[derive(Subcommand)]
14+
pub enum Commands {
15+
Wallet {
16+
#[command(subcommand)]
17+
action: WalletAction,
18+
},
19+
Deploy {
20+
#[arg(long)]
21+
path: String,
22+
#[arg(long, hide = true)]
23+
password: Option<String>,
24+
contract: String,
25+
},
26+
Compile,
27+
}
28+
29+
#[derive(Subcommand)]
30+
pub enum WalletAction {
31+
New,
32+
Balance {
33+
#[arg(long)]
34+
path: String,
35+
#[arg(long, hide = true)]
36+
password: Option<String>,
37+
},
38+
}

cli/src/helpers.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
use ethers::providers::{Http, Provider};
2+
use rpassword::prompt_password;
3+
4+
/// Retrieves a wallet from a keystore file.
5+
///
6+
/// This function provides user prompting for password input when needed,
7+
/// but delegates the actual keystore loading and wallet creation to the
8+
/// `client::wallet_from_keystore` function which contains the core logic.
9+
///
10+
/// # Arguments
11+
/// * `path` - Path to the keystore file
12+
/// * `provider` - Optional Ethereum provider
13+
/// * `password` - Optional password (will prompt user if None)
14+
///
15+
/// # Returns
16+
/// A Result containing the loaded LocalWallet or an error
17+
pub async fn get_wallet_from_keystore(
18+
path: &str,
19+
provider: Option<Provider<Http>>,
20+
password: Option<String>,
21+
) -> anyhow::Result<ethers::signers::LocalWallet> {
22+
let password = match password {
23+
Some(pass) => pass,
24+
None => prompt_password("Enter password: ")?,
25+
};
26+
core::client::wallet_from_keystore(path, provider, &password).await
27+
}

cli/src/main.rs

Lines changed: 34 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,32 @@
1-
use clap::{Parser, Subcommand};
1+
pub mod cli;
2+
pub mod helpers;
3+
4+
use crate::cli::{Cli, Commands, WalletAction};
5+
use clap::Parser as _;
26
use core::compiler::compile_contract;
3-
use core::{client, deployer};
4-
use ethers::providers::{Http, Middleware, Provider};
7+
use core::deployer;
8+
use core::provider::get_providers_with_endpoints;
9+
use ethers::providers::Middleware;
510
use ethers::signers::Signer;
611
use ethers::types::Address;
7-
use foundry_config::Config;
8-
use rpassword::prompt_password;
912
use std::process::Command;
1013

11-
#[derive(Parser)]
12-
#[command(name = "deploy-cli")]
13-
#[command(about = "Deploy contracts and manage deployer", long_about = None)]
14-
struct Cli {
15-
#[arg(long)]
16-
chain: String,
17-
18-
#[command(subcommand)]
19-
command: Commands,
20-
}
21-
#[derive(Subcommand)]
22-
enum Commands {
23-
Wallet {
24-
#[command(subcommand)]
25-
action: WalletAction,
26-
},
27-
Deploy {
28-
#[arg(long)]
29-
path: String,
30-
#[arg(long, hide = true)]
31-
password: Option<String>,
32-
contract: String,
33-
},
34-
Compile,
35-
}
36-
37-
#[derive(Subcommand)]
38-
enum WalletAction {
39-
New,
40-
Balance {
41-
#[arg(long)]
42-
path: String,
43-
#[arg(long, hide = true)]
44-
password: Option<String>,
45-
},
46-
}
47-
48-
async fn get_wallet_from_keystore(
49-
path: &str,
50-
password: Option<String>,
51-
) -> anyhow::Result<ethers::signers::LocalWallet> {
52-
let password = match password {
53-
Some(pass) => pass,
54-
None => prompt_password("Enter password: ")?,
55-
};
56-
client::wallet_from_keystore(path, &password).await
57-
}
58-
59-
fn get_provider(chain_alias: &str) -> anyhow::Result<Provider<Http>> {
60-
let config = Config::load();
61-
62-
let rpc_url: String = config
63-
.get_rpc_url_with_alias(chain_alias)
64-
.ok_or_else(|| anyhow::anyhow!("Error trying to get RPC URL from config"))?
65-
.map_err(|e| anyhow::anyhow!("Error getting RPC URL: {}", e))?
66-
.into_owned();
67-
68-
Provider::<Http>::try_from(rpc_url)
69-
.map_err(|e| anyhow::anyhow!("Error instantiating provider: {}", e))
70-
}
71-
7214
#[tokio::main]
7315
async fn main() -> anyhow::Result<()> {
7416
dotenvy::dotenv().ok();
7517

7618
let cli = Cli::parse();
7719
let chain = cli.chain;
78-
let provider = get_provider(chain.as_str()).expect("Provider setup failed");
20+
21+
let providers = match get_providers_with_endpoints() {
22+
Ok(providers) => providers,
23+
Err(e) => return Err(e),
24+
};
25+
26+
let rpc_info = match providers.get(chain.as_str()) {
27+
Some(rpc_info) => rpc_info.clone(),
28+
None => return Err(anyhow::anyhow!("Chain '{}' not found", chain)),
29+
};
7930

8031
match cli.command {
8132
Commands::Wallet { action } => match action {
@@ -96,9 +47,16 @@ async fn main() -> anyhow::Result<()> {
9647
);
9748
}
9849
WalletAction::Balance { path, password } => {
99-
let wallet = get_wallet_from_keystore(path.as_str(), password).await?;
50+
let wallet = helpers::get_wallet_from_keystore(
51+
path.as_str(),
52+
Some(rpc_info.clone().provider),
53+
password,
54+
)
55+
.await?;
56+
10057
let addr: Address = wallet.address();
101-
let balance = provider.get_balance(addr, None).await?;
58+
let balance = rpc_info.provider.get_balance(addr, None).await?;
59+
10260
println!(
10361
"Deployer: {:?}\nBalance: {} ETH",
10462
addr,
@@ -111,8 +69,13 @@ async fn main() -> anyhow::Result<()> {
11169
password,
11270
contract,
11371
} => {
114-
let wallet = get_wallet_from_keystore(path.as_str(), password).await?;
115-
let addr = deployer::deploy_contract(&contract, provider, wallet).await?;
72+
let wallet = helpers::get_wallet_from_keystore(
73+
path.as_str(),
74+
Some(rpc_info.provider.clone()),
75+
password,
76+
)
77+
.await?;
78+
let addr = deployer::deploy_oapp_contract(rpc_info, wallet).await?;
11679
println!("Deployed `{}` at {:?}\n", contract, addr);
11780
}
11881
Commands::Compile => {

core/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ serde_json = "1"
1010
anyhow = "1"
1111
tokio = { version = "1", features = ["full"] }
1212
foundry-compilers = "0.10.1"
13+
foundry-config = "0.2"

core/src/artifacts.rs

Lines changed: 0 additions & 37 deletions
This file was deleted.

core/src/client.rs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,27 @@
11
use anyhow::Result;
2-
use ethers::signers::{LocalWallet, Signer, Wallet};
2+
use ethers::{
3+
providers::{Http, Middleware, Provider},
4+
signers::{LocalWallet, Signer, Wallet},
5+
};
36
use std::path::Path;
47

58
/// Load a wallet from a keystore file.
6-
/// `cast wallet new .`
7-
pub async fn wallet_from_keystore(path: &str, password: &str) -> Result<LocalWallet> {
9+
/// `cast wallet new .` is needed to call from CLI first.
10+
pub async fn wallet_from_keystore(
11+
path: &str,
12+
provider: Option<Provider<Http>>,
13+
password: &str,
14+
) -> Result<LocalWallet> {
815
let path = Path::new(path);
916
let mut wallet = Wallet::decrypt_keystore(&path, password)?;
1017

11-
// TODO Replace with chain id from provider
12-
let chain_id: u64 = 84532;
13-
wallet = wallet.with_chain_id(chain_id);
18+
match provider {
19+
Some(provider) => {
20+
let chain_id = provider.get_chainid().await?.as_u64();
21+
wallet = wallet.with_chain_id(chain_id);
22+
}
23+
None => (),
24+
}
1425

1526
println!("Wallet loaded successfully.");
1627
Ok(wallet)

core/src/deployer.rs

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,17 @@
1-
use crate::artifacts;
1+
use crate::provider::RpcInfo;
22
use anyhow::Result;
33
use ethers::prelude::*;
44
use std::sync::Arc;
55

6-
pub async fn deploy_contract(
7-
contract_name: &str,
8-
provider: Provider<Http>,
9-
wallet: LocalWallet,
10-
) -> Result<Address> {
11-
// TODO Extract
12-
let endpoint_address = "0x6EDCE65403992e310A62460808c4b910D972f10f".parse::<Address>()?;
13-
let delegator_address = "0x6EDCE65403992e310A62460808c4b910D972f10f".parse::<Address>()?;
6+
pub async fn deploy_oapp_contract(rpc_info: RpcInfo, wallet: LocalWallet) -> Result<Address> {
7+
let endpoint_address = rpc_info.endpoint;
8+
let delegator_address = wallet.address();
149

15-
// FIXME abi is not used, is replaced by abigen! macro. Remove from artifacts module
16-
// let abi = artifacts::load_abi(contract_name)?;
17-
// TODO Move closer to artifact reading
10+
// FIXME Hardcoded string
1811
abigen!(MyOApp, "core/src/solidity/artifacts/MyOApp.sol/MyOApp.json");
1912

20-
// FIXME bytecode is not used, is replaced by abigen! macro. Remove from artifacts module
21-
// let bytecode = artifacts::load_bytecode(contract_name)?;
22-
let client = Arc::new(SignerMiddleware::new(provider, wallet));
13+
let client = Arc::new(SignerMiddleware::new(rpc_info.clone().provider, wallet));
2314

24-
// Tuples are used.
2515
let deployed = MyOApp::deploy(client, (endpoint_address, delegator_address))
2616
.map_err(|e| {
2717
println!("Deployment preparation error: {:?}", e);

core/src/layer_zero.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
use std::sync::Arc;
2+
3+
use ethers::prelude::*;
4+
use ethers::types::H256;
5+
6+
use crate::deployer::deploy_oapp_contract;
7+
use crate::provider::get_providers_with_endpoints;
8+
9+
// TODO Make strings an enum of possible chains
10+
pub async fn deploy_on_chains(
11+
chains: &[String],
12+
deployer: &LocalWallet,
13+
) -> Result<(), anyhow::Error> {
14+
let mut addresses = Vec::new();
15+
16+
let providers = get_providers_with_endpoints()?;
17+
18+
// Deploy the oapp on each chain
19+
for chain in chains {
20+
println!("Deploying provider on chain {}", chain);
21+
22+
let rpc_info_for_current_chain = if let Some(rpc_info) = providers.get(chain) {
23+
rpc_info.clone()
24+
} else {
25+
println!("Chain {} not found in providers, skipping", chain);
26+
continue;
27+
};
28+
29+
let chain_id = rpc_info_for_current_chain
30+
.provider
31+
.get_chainid()
32+
.await?
33+
.as_u64();
34+
let wallet = deployer.clone().with_chain_id(chain_id);
35+
36+
let oapp_addr =
37+
deploy_oapp_contract(rpc_info_for_current_chain.clone(), wallet.clone()).await?;
38+
addresses.push((chain.clone(), oapp_addr));
39+
}
40+
41+
abigen!(MyOApp, "core/src/solidity/artifacts/MyOApp.sol/MyOApp.json");
42+
43+
// Set up peer connections (each with n-1 others)
44+
for (i, (chain_i, addr_i)) in addresses.iter().enumerate() {
45+
for (j, (chain_j, addr_j)) in addresses.iter().enumerate() {
46+
if i != j {
47+
println!("Connecting {} to {}", chain_i, chain_j);
48+
// Instantiate OApp contract using its address
49+
50+
let rpc_info_for_current_chain = if let Some(rpc_info) = providers.get(chain_i) {
51+
rpc_info.clone()
52+
} else {
53+
println!("Chain {} not found in providers, skipping", chain_i);
54+
continue;
55+
};
56+
57+
let client = Arc::new(SignerMiddleware::new(
58+
rpc_info_for_current_chain.clone().provider,
59+
deployer.clone(),
60+
));
61+
let oapp_contract = MyOApp::new(*addr_i, client);
62+
63+
// Convert the address to the 20-byte format required by LayerZero
64+
let peer_addr = H256::from(*addr_j).to_fixed_bytes();
65+
let eid = match chain_i.as_str() {
66+
"base_sepolia" => 40245,
67+
"optimism_sepolia" => 40232,
68+
_ => 1,
69+
};
70+
// oapp_contract
71+
oapp_contract.set_peer(eid, peer_addr).await?;
72+
}
73+
}
74+
}
75+
76+
println!("Layer Zero network successfully deployed and connected!");
77+
Ok(())
78+
}

0 commit comments

Comments
 (0)