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();