Skip to content

Commit

Permalink
feat: add dapp-portal support to zk_inception (#2659)
Browse files Browse the repository at this point in the history
## What❔

This PR introduces a new `portal` subcommand to the `zk_inception` CLI
tool, enabling users to easily launch the
[dapp-portal](https://github.com/matter-labs/dapp-portal) for their
deployed chains.

Usage: `zk_inception portal [--port 3030]`

The ecosystem configurations are automatically converted to the
[hyperchains](https://github.com/matter-labs/dapp-portal/tree/main/hyperchains#%EF%B8%8F-configure-manually)
format, which is used to configure dapp-portal at runtime. Essentially,
the following command is executed under the hood:
`docker run -p PORT:3000
/path/to/portal.config.js:/usr/src/app/dist/config.js dapp-portal`

## Why ❔

Currently, running the dapp-portal requires users to manually pull the
repository, install all dependencies, modify configurations, build the
project, and then run it - a tedious and time-consuming process. This PR
simplifies the process, allowing users to run the portal effortlessly
with a single command.

## Checklist

- [x] PR title corresponds to the body of PR (we generate changelog
entries from PRs).
- [x] Tests for the changes have been added / updated.
- [] Documentation comments have been added / updated.
- [x] Code has been formatted via `zk fmt` and `zk lint`.
  • Loading branch information
sanekmelnikov authored Aug 20, 2024
1 parent 16dff4f commit 835d2d3
Show file tree
Hide file tree
Showing 17 changed files with 397 additions and 8 deletions.
1 change: 1 addition & 0 deletions zk_toolbox/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions zk_toolbox/crates/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ serde_yaml.workspace = true
sqlx.workspace = true
tokio.workspace = true
toml.workspace = true
types.workspace = true
url.workspace = true
xshell.workspace = true
thiserror.workspace = true
Expand Down
15 changes: 15 additions & 0 deletions zk_toolbox/crates/common/src/docker.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::collections::HashMap;

use xshell::{cmd, Shell};

use crate::cmd::Cmd;
Expand All @@ -9,3 +11,16 @@ pub fn up(shell: &Shell, docker_compose_file: &str) -> anyhow::Result<()> {
pub fn down(shell: &Shell, docker_compose_file: &str) -> anyhow::Result<()> {
Ok(Cmd::new(cmd!(shell, "docker compose -f {docker_compose_file} down")).run()?)
}

pub fn run(
shell: &Shell,
docker_image: &str,
docker_args: HashMap<String, String>,
) -> anyhow::Result<()> {
let mut args = vec![];
for (key, value) in docker_args.iter() {
args.push(key);
args.push(value);
}
Ok(Cmd::new(cmd!(shell, "docker run {args...} {docker_image}")).run()?)
}
19 changes: 19 additions & 0 deletions zk_toolbox/crates/common/src/ethereum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use ethers::{
providers::Middleware,
types::{Address, TransactionRequest, H256},
};
use types::TokenInfo;

use crate::{logger, wallets::Wallet};

Expand Down Expand Up @@ -58,10 +59,28 @@ pub async fn distribute_eth(
abigen!(
TokenContract,
r"[
function name() external view returns (string)
function symbol() external view returns (string)
function decimals() external view returns (uint8)
function mint(address to, uint256 amount)
]"
);

pub async fn get_token_info(token_address: Address, rpc_url: String) -> anyhow::Result<TokenInfo> {
let provider = Provider::<Http>::try_from(rpc_url)?;
let contract = TokenContract::new(token_address, Arc::new(provider));

let name = contract.name().call().await?;
let symbol = contract.symbol().call().await?;
let decimals = contract.decimals().call().await?;

Ok(TokenInfo {
name,
symbol,
decimals,
})
}

pub async fn mint_token(
main_wallet: Wallet,
token_address: Address,
Expand Down
3 changes: 3 additions & 0 deletions zk_toolbox/crates/config/src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ pub const ERA_OBSERBAVILITY_GIT_REPO: &str = "https://github.com/matter-labs/era
pub(crate) const LOCAL_CONFIGS_PATH: &str = "configs/";
pub(crate) const LOCAL_DB_PATH: &str = "db/";

/// Name of portal config file
pub const PORTAL_CONFIG_FILE: &str = "portal.config.js";

/// Path to ecosystem contacts
pub(crate) const ECOSYSTEM_PATH: &str = "etc/env/ecosystems";

Expand Down
1 change: 1 addition & 0 deletions zk_toolbox/crates/config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ mod wallets;

pub mod external_node;
pub mod forge_interface;
pub mod portal;
pub mod traits;
124 changes: 124 additions & 0 deletions zk_toolbox/crates/config/src/portal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
use std::path::{Path, PathBuf};

use serde::{Deserialize, Serialize};
use types::TokenInfo;
use xshell::Shell;

use crate::{
consts::{LOCAL_CONFIGS_PATH, PORTAL_CONFIG_FILE},
traits::{FileConfigWithDefaultName, ReadConfig, SaveConfig},
};

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct PortalRuntimeConfig {
pub node_type: String,
pub hyperchains_config: HyperchainsConfig,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct HyperchainsConfig(pub Vec<HyperchainConfig>);

impl HyperchainsConfig {
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct HyperchainConfig {
pub network: NetworkConfig,
pub tokens: Vec<TokenConfig>,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct NetworkConfig {
pub id: u64, // L2 Network ID
pub key: String, // L2 Network key
pub name: String, // L2 Network name
pub rpc_url: String, // L2 RPC URL
#[serde(skip_serializing_if = "Option::is_none")]
pub block_explorer_url: Option<String>, // L2 Block Explorer URL
#[serde(skip_serializing_if = "Option::is_none")]
pub block_explorer_api: Option<String>, // L2 Block Explorer API
#[serde(skip_serializing_if = "Option::is_none")]
pub public_l1_network_id: Option<u64>, // Ethereum Mainnet or Ethereum Sepolia Testnet ID
#[serde(skip_serializing_if = "Option::is_none")]
pub l1_network: Option<L1NetworkConfig>,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct L1NetworkConfig {
pub id: u64,
pub name: String,
pub network: String,
pub native_currency: TokenInfo,
pub rpc_urls: RpcUrls,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct RpcUrls {
pub default: RpcUrlConfig,
pub public: RpcUrlConfig,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct RpcUrlConfig {
pub http: Vec<String>,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct TokenConfig {
pub address: String,
pub symbol: String,
pub decimals: u8,
#[serde(skip_serializing_if = "Option::is_none")]
pub l1_address: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
}

impl PortalRuntimeConfig {
pub fn get_config_path(ecosystem_base_path: &Path) -> PathBuf {
ecosystem_base_path
.join(LOCAL_CONFIGS_PATH)
.join(PORTAL_CONFIG_FILE)
}
}

impl FileConfigWithDefaultName for PortalRuntimeConfig {
const FILE_NAME: &'static str = PORTAL_CONFIG_FILE;
}

impl SaveConfig for PortalRuntimeConfig {
fn save(&self, shell: &Shell, path: impl AsRef<Path>) -> anyhow::Result<()> {
// The dapp-portal is served as a pre-built static app in a Docker image.
// It uses a JavaScript file (config.js) that injects the configuration at runtime
// by overwriting the '##runtimeConfig' property of the window object.
// Therefore, we generate a JavaScript file instead of a JSON file.
// This file will be mounted to the Docker image when it runs.
let json = serde_json::to_string_pretty(&self)?;
let config_js_content = format!("window['##runtimeConfig'] = {};", json);
Ok(shell.write_file(path, config_js_content.as_bytes())?)
}
}

impl ReadConfig for PortalRuntimeConfig {
fn read(shell: &Shell, path: impl AsRef<Path>) -> anyhow::Result<Self> {
let config_js_content = shell.read_file(path)?;
// Extract the JSON part from the JavaScript file
let json_start = config_js_content
.find('{')
.ok_or_else(|| anyhow::anyhow!("Invalid config file format"))?;
let json_end = config_js_content
.rfind('}')
.ok_or_else(|| anyhow::anyhow!("Invalid config file format"))?;
let json_str = &config_js_content[json_start..=json_end];
// Parse the JSON into PortalRuntimeConfig
let config: PortalRuntimeConfig = serde_json::from_str(json_str)?;
Ok(config)
}
}
2 changes: 2 additions & 0 deletions zk_toolbox/crates/types/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
mod base_token;
mod l1_network;
mod prover_mode;
mod token_info;
mod wallet_creation;

pub use base_token::*;
pub use l1_network::*;
pub use prover_mode::*;
pub use token_info::*;
pub use wallet_creation::*;
pub use zksync_basic_types::{
commitment::L1BatchCommitmentMode, protocol_version::ProtocolSemanticVersion,
Expand Down
18 changes: 18 additions & 0 deletions zk_toolbox/crates/types/src/token_info.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct TokenInfo {
pub name: String,
pub symbol: String,
pub decimals: u8,
}

impl TokenInfo {
pub fn eth() -> Self {
Self {
name: "Ether".to_string(),
symbol: "ETH".to_string(),
decimals: 18,
}
}
}
2 changes: 2 additions & 0 deletions zk_toolbox/crates/zk_inception/src/commands/args/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
pub use containers::*;
pub use portal::*;
pub use run_server::*;
pub use update::*;

mod containers;
mod portal;
mod run_server;
mod update;
12 changes: 12 additions & 0 deletions zk_toolbox/crates/zk_inception/src/commands/args/portal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use clap::Parser;
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize, Parser)]
pub struct PortalArgs {
#[clap(
long,
default_value = "3030",
help = "The port number for the portal app"
)]
pub port: u16,
}
20 changes: 14 additions & 6 deletions zk_toolbox/crates/zk_inception/src/commands/chain/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,21 @@ use xshell::Shell;

use crate::{
accept_ownership::accept_admin,
commands::chain::{
args::init::{InitArgs, InitArgsFinal},
deploy_l2_contracts, deploy_paymaster,
genesis::genesis,
set_token_multiplier_setter::set_token_multiplier_setter,
commands::{
chain::{
args::init::{InitArgs, InitArgsFinal},
deploy_l2_contracts, deploy_paymaster,
genesis::genesis,
set_token_multiplier_setter::set_token_multiplier_setter,
},
portal::create_and_save_portal_config,
},
consts::AMOUNT_FOR_DISTRIBUTION_TO_WALLETS,
messages::{
msg_initializing_chain, MSG_ACCEPTING_ADMIN_SPINNER, MSG_CHAIN_INITIALIZED,
MSG_CHAIN_NOT_FOUND_ERR, MSG_DISTRIBUTING_ETH_SPINNER, MSG_GENESIS_DATABASE_ERR,
MSG_MINT_BASE_TOKEN_SPINNER, MSG_REGISTERING_CHAIN_SPINNER, MSG_SELECTED_CONFIG,
MSG_MINT_BASE_TOKEN_SPINNER, MSG_PORTAL_FAILED_TO_CREATE_CONFIG_ERR,
MSG_REGISTERING_CHAIN_SPINNER, MSG_SELECTED_CONFIG,
MSG_UPDATING_TOKEN_MULTIPLIER_SETTER_SPINNER,
},
utils::forge::{check_the_balance, fill_forge_private_key},
Expand Down Expand Up @@ -145,6 +149,10 @@ pub async fn init(
.await
.context(MSG_GENESIS_DATABASE_ERR)?;

create_and_save_portal_config(ecosystem_config, shell)
.await
.context(MSG_PORTAL_FAILED_TO_CREATE_CONFIG_ERR)?;

Ok(())
}

Expand Down
1 change: 1 addition & 0 deletions zk_toolbox/crates/zk_inception/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub mod containers;
pub mod contract_verifier;
pub mod ecosystem;
pub mod external_node;
pub mod portal;
pub mod prover;
pub mod server;
pub mod update;
Loading

0 comments on commit 835d2d3

Please sign in to comment.