diff --git a/zkevm-contract-bindings/src/bin/deploy.rs b/zkevm-contract-bindings/src/bin/deploy.rs index 3689da6..2712f4b 100644 --- a/zkevm-contract-bindings/src/bin/deploy.rs +++ b/zkevm-contract-bindings/src/bin/deploy.rs @@ -10,15 +10,17 @@ use async_compatibility_layer::logging::{setup_backtrace, setup_logging}; use clap::Parser; use contract_bindings::HotShot; use ethers::{ + prelude::{NonceManagerMiddleware, SignerMiddleware}, providers::{Http, Middleware, Provider}, + signers::{coins_bip39::English, MnemonicBuilder, Signer}, types::Address, utils::{get_contract_address, parse_ether}, }; use hex::{FromHex, FromHexError}; -use sequencer_utils::{connect_rpc, Middleware as EthMiddleware}; +use sequencer_utils::Middleware as EthMiddleware; use serde::{Deserialize, Serialize}; use serde_with::with_prefix; -use std::path::PathBuf; +use std::{num::ParseIntError, path::PathBuf}; use std::{sync::Arc, time::Duration}; use url::Url; use zkevm_contract_bindings::{ @@ -43,6 +45,10 @@ pub struct Options { )] pub mnemonic: String, + /// The account index of the deployer wallet. + #[arg(long, env = "ESPRESSO_ZKEVM_DEPLOY_ACCOUNT_INDEX", default_value = "0")] + pub account_index: u32, + /// The URL of an Ethereum JsonRPC where the contracts will be deployed. #[arg( long, @@ -51,6 +57,16 @@ pub struct Options { )] pub provider_url: Url, + /// The address of the hotshot contract, if already deployed. + #[arg(long, env = "ESPRESSO_SEQUENCER_HOTSHOT_ADDRESS")] + pub hotshot_address: Option
, + + /// Whether to deploy the second zkevm-node. + /// + /// If false, only the contracts for the first rollup are deployed. + #[arg(long, env = "ESPRESSO_ZKEVM_DEPLOY_ROLLUP_2", default_value = "false")] + pub deploy_rollup_2: bool, + /// Wallet address of the trusted aggregator for the first zkevm. /// /// This needs to the address of the wallet that the zkevm aggregator @@ -95,10 +111,25 @@ pub struct Options { #[arg( short, long, - env = "ESPRESSO_ZKEVM_DEPLOY_OUTPUT", + env = "ESPRESSO_ZKEVM_DEPLOY_OUTPUT_PATH", default_value = "deployment.env" )] pub output_path: PathBuf, + + /// Output file path where deployment info will be stored. + #[arg(long, env = "ESPRESSO_ZKEVM_DEPLOY_OUTPUT_JSON")] + pub json: bool, + + /// Polling interval for the RPC provider + /// + /// By default the ether-rs default of 7 seconds will be used. + #[arg( + short, + long, + env = "ESPRESSO_ZKEVM_DEPLOY_POLLING_INTERVAL_MS", + value_parser = |arg: &str| -> Result { Ok(Duration::from_millis(arg.parse()?)) } + )] + pub polling_interval: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -155,14 +186,14 @@ struct DeploymentOutput { /// The second polygon zkevm. #[serde(flatten, with = "prefix_zkevm_2")] - zkevm_2_input: ZkEvmDeploymentInput, + zkevm_2_input: Option, #[serde(flatten, with = "prefix_zkevm_2")] - zkevm_2_output: ZkEvmDeploymentOutput, + zkevm_2_output: Option, } impl DeploymentOutput { fn to_dotenv(&self) -> String { - let mut dotenv = "# Deployment configuration\n".to_owned(); + let mut dotenv = "# Deployment configuration (generated by deploy.rs)\n".to_owned(); let json = serde_json::to_value(self).unwrap(); for (key, val) in json.as_object().unwrap() { dotenv = format!("{dotenv}{key}={val}\n") @@ -279,18 +310,91 @@ async fn deploy_zkevm( }) } +pub async fn connect_rpc( + provider: &Url, + mnemonic: &str, + index: u32, + chain_id: Option, + polling_interval: Option, +) -> Option> { + let mut provider = match Provider::try_from(provider.to_string()) { + Ok(provider) => provider, + Err(err) => { + tracing::error!("error connecting to RPC {}: {}", provider, err); + return None; + } + }; + tracing::info!("Connected to RPC {}", provider.url()); + + if let Some(interval) = polling_interval { + provider.set_interval(interval); + } + tracing::info!("RPC Polling interval is {:?}", provider.get_interval()); + + let chain_id = match chain_id { + Some(id) => id, + None => match provider.get_chainid().await { + Ok(id) => id.as_u64(), + Err(err) => { + tracing::error!("error getting chain ID: {}", err); + return None; + } + }, + }; + tracing::info!("Chain ID is {}", chain_id); + + let mnemonic = match MnemonicBuilder::::default() + .phrase(mnemonic) + .index(index) + { + Ok(mnemonic) => mnemonic, + Err(err) => { + tracing::error!("error building walletE: {}", err); + return None; + } + }; + let wallet = match mnemonic.build() { + Ok(wallet) => wallet, + Err(err) => { + tracing::error!("error opening wallet: {}", err); + return None; + } + }; + let wallet = wallet.with_chain_id(chain_id); + let address = wallet.address(); + Some(Arc::new(NonceManagerMiddleware::new( + SignerMiddleware::new(provider, wallet), + address, + ))) +} + async fn deploy(opts: Options) -> Result<()> { let mut provider = Provider::try_from(opts.provider_url.to_string())?; - provider.set_interval(Duration::from_millis(100)); - let deployer = connect_rpc(&opts.provider_url, &opts.mnemonic, 0, None) - .await - .unwrap(); + if let Some(interval) = opts.polling_interval { + provider.set_interval(interval); + } + + let deployer = connect_rpc( + &opts.provider_url, + &opts.mnemonic, + opts.account_index, + None, + opts.polling_interval, + ) + .await + .unwrap(); tracing::info!("Using deployer account {:?}", deployer.inner().address()); // Deploy the hotshot contract. - let hotshot = HotShot::deploy(deployer.clone(), ())?.send().await?; - let hotshot_address = hotshot.address(); - tracing::info!("Deployed HotShot at {:?}", hotshot.address()); + let hotshot_address = if opts.hotshot_address.is_none() { + tracing::info!("Deploying HotShot contract"); + let hotshot = HotShot::deploy(deployer.clone(), ())?.send().await?; + tracing::info!("Deployed HotShot at {:?}", hotshot.address()); + hotshot.address() + } else { + tracing::info!("Using existing HotShot contract"); + opts.hotshot_address.unwrap() + }; // Deploy the contracts for the first zkevm-node. let zkevm_1_input = ZkEvmDeploymentInput { @@ -303,16 +407,23 @@ async fn deploy(opts: Options) -> Result<()> { }; let zkevm_1_output = deploy_zkevm(&provider, deployer.clone(), &zkevm_1_input).await?; - // Deploy the contracts for the second zkevm-node. - let zkevm_2_input = ZkEvmDeploymentInput { - hotshot_address, - trusted_aggregator: opts.trusted_aggregator_2, - genesis_root: opts.genesis_root_2, - chain_id: 1002u64, - fork_id: 1u64, - network_name: "zkevm-two".to_string(), + let (zkevm_2_input, zkevm_2_output) = if opts.deploy_rollup_2 { + tracing::info!("Deploying second zkevm-node"); + let zkevm_2_input = ZkEvmDeploymentInput { + hotshot_address, + trusted_aggregator: opts.trusted_aggregator_2, + genesis_root: opts.genesis_root_2, + chain_id: 1002u64, + fork_id: 1u64, + network_name: "zkevm-two".to_string(), + }; + let zkevm_2_output = deploy_zkevm(&provider, deployer.clone(), &zkevm_2_input).await?; + (Some(zkevm_2_input), Some(zkevm_2_output)) + } else { + tracing::info!("Not deploying second zkevm-node"); + (None, None) }; - let zkevm_2_output = deploy_zkevm(&provider, deployer.clone(), &zkevm_2_input).await?; + // Deploy the contracts for the second zkevm-node. // Save the output to a file. let output = DeploymentOutput { @@ -323,8 +434,13 @@ async fn deploy(opts: Options) -> Result<()> { zkevm_2_output, }; - std::fs::write(&opts.output_path, output.to_dotenv())?; + let content = if opts.json { + serde_json::to_string_pretty(&output)? + } else { + output.to_dotenv() + }; tracing::info!("Wrote deployment output to {}", opts.output_path.display()); + std::fs::write(&opts.output_path, content)?; Ok(()) } @@ -345,12 +461,30 @@ mod test { use tempfile::NamedTempFile; #[async_std::test] - async fn test_run_deploy_scripts() -> Result<()> { + async fn test_run_deploy_script_two_rollups() -> Result<()> { + setup_logging(); + setup_backtrace(); + + let anvil = AnvilOptions::default().spawn().await; + let mut opts = Options::parse_from([""]); + opts.polling_interval = Some(Duration::from_millis(10)); + opts.deploy_rollup_2 = true; + opts.provider_url = anvil.url(); + opts.output_path = NamedTempFile::new()?.path().to_path_buf(); + + deploy(opts).await?; + + Ok(()) + } + + #[async_std::test] + async fn test_run_deploy_script_one_rollup() -> Result<()> { setup_logging(); setup_backtrace(); let anvil = AnvilOptions::default().spawn().await; let mut opts = Options::parse_from([""]); + opts.polling_interval = Some(Duration::from_millis(10)); opts.provider_url = anvil.url(); opts.output_path = NamedTempFile::new()?.path().to_path_buf();