Skip to content

Commit

Permalink
feat(zk_toolbox): add zki ecosystem build subcommand (#2787)
Browse files Browse the repository at this point in the history
## What ❔

<!-- What are the changes this PR brings about? -->
<!-- Example: This PR adds a PR template to the repo. -->
<!-- (For bigger PRs adding more context is appreciated) -->
Add `zki ecosystem build` subcommand, which builds L1 transactions
without signing and broadcasting them.
This allows a security team to sign them using keys in cold storage and
multisig.

```bash
Create transactions to build ecosystem contracts

Usage: zki ecosystem build-transactions [OPTIONS]

Options:
      --sender <SENDER>
          Address of the transaction sender

      --l1-rpc-url <L1_RPC_URL>
          L1 RPC URL

  -o, --out <OUT>
          Output directory for the generated files

  -h, --help
          Print help (see a summary with '-h')
```

```bash
Create unsigned transactions for chain deployment

Usage: zki chain build-transactions [OPTIONS]

Options:
  -o, --out <OUT>
          Output directory for the generated files

  -h, --help
          Print help (see a summary with '-h')
```

### Output
```json
# /transactions/deploy.json
{
  "transactions": [
    {
      "hash": null,
      "transactionType": "CREATE",
      "contractName": null,
      "contractAddress": "0xddca24376aa96d8f56667d78306a3bbbdc65b1ff",
      "function": null,
      "arguments": null,
      "transaction": {
        "from": "0xaf9d732e8a5607caccb72df525851849c33edf9e",
        "gas": "0x15a02",
        "value": "0x0",
        "input": "0x604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3",
        "nonce": "0x0",
        "chainId": "0x9"
      },
      "additionalContracts": [],
      "isFixedGasLimit": false
    },
    {
      "hash": null,
      "transactionType": "CALL",
      "contractName": null,
      "contractAddress": "0xddca24376aa96d8f56667d78306a3bbbdc65b1ff",
      "function": null,
      "arguments": null,
      "transaction": {
        "from": "0xaf9d732e8a5607caccb72df525851849c33edf9e",
        "to": "0xddca24376aa96d8f56667d78306a3bbbdc65b1ff",
        "gas": "0xfb603",
        "value": "0x0",
        "input": ...
```



## Why ❔

<!-- Why are these changes done? What goal do they contribute to? What
are the principles behind them? -->
<!-- Example: PR templates ensure PR reviewers, observers, and future
iterators are in context about the evolution of repos. -->

## Checklist

<!-- Check your PR fulfills the following items. -->
<!-- For draft PRs check the boxes as you complete them. -->

- [x] PR title corresponds to the body of PR (we generate changelog
entries from PRs).
- [x] Tests for the changes have been added / updated.
- [x] Documentation comments have been added / updated.
- [x] Code has been formatted via `zk fmt` and `zk lint`.
  • Loading branch information
manuelmauro authored Sep 16, 2024
1 parent 9d40704 commit 73c0b7c
Show file tree
Hide file tree
Showing 30 changed files with 881 additions and 206 deletions.
38 changes: 35 additions & 3 deletions .github/workflows/ci-zk-toolbox-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ jobs:
submodules: "recursive"
fetch-depth: 0


- name: Setup environment
run: |
echo ZKSYNC_HOME=$(pwd) >> $GITHUB_ENV
Expand Down Expand Up @@ -79,6 +78,41 @@ jobs:
--ignore-prerequisites --verbose \
--observability=false
- name: Create and register chain with transactions signed "offline"
run: |
ci_run zk_inception chain create \
--chain-name offline_chain \
--chain-id sequential \
--prover-mode no-proofs \
--wallet-creation localhost \
--l1-batch-commit-data-generator-mode rollup \
--base-token-address 0x0000000000000000000000000000000000000001 \
--base-token-price-nominator 1 \
--base-token-price-denominator 1 \
--set-as-default false \
--ignore-prerequisites
ci_run zk_inception chain build-transactions --chain offline_chain --l1-rpc-url http://127.0.0.1:8545
governor_pk=$(awk '/governor:/ {flag=1} flag && /private_key:/ {print $2; exit}' ./configs/wallets.yaml)
ci_run zk_supervisor send-transactions \
--file ./transactions/chain/offline_chain/register-hyperchain-txns.json \
--l1-rpc-url http://127.0.0.1:8545 \
--private-key $governor_pk
bridge_hub=$(awk '/bridgehub_proxy_addr/ {print $2}' ./configs/contracts.yaml)
chain_id=$(awk '/chain_id:/ {print $2}' ./chains/offline_chain/ZkStack.yaml)
hyperchain_output=$(ci_run cast call $bridge_hub "getHyperchain(uint256)" $chain_id)
if [[ $hyperchain_output == 0x* && ${#hyperchain_output} -eq 66 ]]; then
echo "Chain successfully registered: $hyperchain_output"
else
echo "Failed to register chain: $hyperchain_output"
exit 1
fi
- name: Read Custom Token address and set as environment variable
run: |
address=$(awk -F": " '/tokens:/ {found_tokens=1} found_tokens && /DAI:/ {found_dai=1} found_dai && /address:/ {print $2; exit}' ./configs/erc20.yaml)
Expand Down Expand Up @@ -297,15 +331,13 @@ jobs:
wait $PID3
wait $PID4
# Upgrade tests should run last, because as soon as they
# finish the bootloader will be different
# TODO make upgrade tests safe to run multiple times
- name: Run upgrade test
run: |
ci_run zk_supervisor test upgrade --no-deps --chain era
- name: Upload logs
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
if: always()
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,4 @@ chains/era/configs/*
configs/*
era-observability/
core/tests/ts-integration/deployments-zk
transactions/
2 changes: 2 additions & 0 deletions zk_toolbox/Cargo.lock

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

2 changes: 1 addition & 1 deletion zk_toolbox/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ zksync_protobuf = "=0.1.1"
# External dependencies
anyhow = "1.0.82"
clap = { version = "4.4", features = ["derive", "wrap_help", "string"] }
chrono = "0.4"
slugify-rs = "0.0.3"
cliclack = "0.2.5"
console = "0.15.8"
chrono = "0.4.38"
ethers = "2.0"
futures = "0.3.30"
human-panic = "2.0"
Expand Down
11 changes: 11 additions & 0 deletions zk_toolbox/crates/common/src/forge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ impl ForgeScript {
self
}

/// Add the sender address to the forge script command.
pub fn with_sender(mut self, address: String) -> Self {
self.args.add_arg(ForgeScriptArg::Sender { address });
self
}

/// Add the rpc-url flag to the forge script command.
pub fn with_rpc_url(mut self, rpc_url: String) -> Self {
self.args.add_arg(ForgeScriptArg::RpcUrl { url: rpc_url });
Expand Down Expand Up @@ -135,6 +141,7 @@ impl ForgeScript {
});
self
}

// Do not start the script if balance is not enough
pub fn private_key(&self) -> Option<H256> {
self.args.args.iter().find_map(|a| {
Expand Down Expand Up @@ -244,6 +251,10 @@ pub enum ForgeScriptArg {
},
Verify,
Resume,
#[strum(to_string = "sender={address}")]
Sender {
address: String,
},
}

/// ForgeScriptArgs is a set of arguments that can be passed to the forge script command.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ impl ZkToolboxConfig for RegisterChainL1Config {}

impl RegisterChainL1Config {
pub fn new(chain_config: &ChainConfig, contracts: &ContractsConfig) -> anyhow::Result<Self> {
let genesis_config = chain_config.get_genesis_config()?;
let wallets_config = chain_config.get_wallets_config()?;
Ok(Self {
contracts_config: Contracts {
Expand All @@ -72,7 +71,7 @@ impl RegisterChainL1Config {
validator_timelock_addr: contracts.ecosystem_contracts.validator_timelock_addr,
},
chain: ChainL1Config {
chain_chain_id: genesis_config.l2_chain_id,
chain_chain_id: chain_config.chain_id,
base_token_gas_price_multiplier_nominator: chain_config.base_token.nominator,
base_token_gas_price_multiplier_denominator: chain_config.base_token.denominator,
base_token_addr: chain_config.base_token.address,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use std::path::PathBuf;

use clap::Parser;
use common::{config::global_config, forge::ForgeScriptArgs, Prompt};
use serde::{Deserialize, Serialize};
use url::Url;

use crate::{
consts::DEFAULT_UNSIGNED_TRANSACTIONS_DIR,
defaults::LOCAL_RPC_URL,
messages::{MSG_L1_RPC_URL_HELP, MSG_L1_RPC_URL_INVALID_ERR, MSG_L1_RPC_URL_PROMPT},
};

const CHAIN_SUBDIR: &str = "chain";

#[derive(Debug, Clone, Serialize, Deserialize, Parser)]
pub struct BuildTransactionsArgs {
/// Output directory for the generated files.
#[arg(long, short)]
pub out: Option<PathBuf>,
/// All ethereum environment related arguments
#[clap(flatten)]
#[serde(flatten)]
pub forge_args: ForgeScriptArgs,
#[clap(long, help = MSG_L1_RPC_URL_HELP)]
pub l1_rpc_url: Option<String>,
}

impl BuildTransactionsArgs {
pub fn fill_values_with_prompt(self, default_chain: String) -> BuildTransactionsArgsFinal {
let chain_name = global_config().chain_name.clone();

let l1_rpc_url = self.l1_rpc_url.unwrap_or_else(|| {
Prompt::new(MSG_L1_RPC_URL_PROMPT)
.default(LOCAL_RPC_URL)
.validate_with(|val: &String| -> Result<(), String> {
Url::parse(val)
.map(|_| ())
.map_err(|_| MSG_L1_RPC_URL_INVALID_ERR.to_string())
})
.ask()
});

BuildTransactionsArgsFinal {
out: self
.out
.unwrap_or(PathBuf::from(DEFAULT_UNSIGNED_TRANSACTIONS_DIR).join(CHAIN_SUBDIR))
.join(chain_name.unwrap_or(default_chain)),
forge_args: self.forge_args,
l1_rpc_url,
}
}
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct BuildTransactionsArgsFinal {
pub out: PathBuf,
pub forge_args: ForgeScriptArgs,
pub l1_rpc_url: String,
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod build_transactions;
pub mod create;
pub mod genesis;
pub mod init;
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use anyhow::Context;
use common::{config::global_config, git, logger, spinner::Spinner};
use config::{
copy_configs, traits::SaveConfigWithBasePath, update_from_chain_config, EcosystemConfig,
};
use ethers::utils::hex::ToHex;
use xshell::Shell;

use super::common::register_chain;
use crate::{
commands::chain::args::build_transactions::BuildTransactionsArgs,
messages::{
MSG_BUILDING_CHAIN_REGISTRATION_TXNS_SPINNER, MSG_CHAIN_NOT_FOUND_ERR,
MSG_CHAIN_TRANSACTIONS_BUILT, MSG_CHAIN_TXN_MISSING_CONTRACT_CONFIG,
MSG_CHAIN_TXN_OUT_PATH_INVALID_ERR, MSG_PREPARING_CONFIG_SPINNER, MSG_SELECTED_CONFIG,
MSG_WRITING_OUTPUT_FILES_SPINNER,
},
};

const REGISTER_CHAIN_TXNS_FILE_SRC: &str =
"contracts/l1-contracts/broadcast/RegisterHyperchain.s.sol/9/dry-run/run-latest.json";
const REGISTER_CHAIN_TXNS_FILE_DST: &str = "register-hyperchain-txns.json";

const SCRIPT_CONFIG_FILE_SRC: &str =
"contracts/l1-contracts/script-config/register-hyperchain.toml";
const SCRIPT_CONFIG_FILE_DST: &str = "register-hyperchain.toml";

pub(crate) async fn run(args: BuildTransactionsArgs, shell: &Shell) -> anyhow::Result<()> {
let config = EcosystemConfig::from_file(shell)?;
let chain_name = global_config().chain_name.clone();
let chain_config = config
.load_chain(chain_name)
.context(MSG_CHAIN_NOT_FOUND_ERR)?;

let args = args.fill_values_with_prompt(config.default_chain.clone());

git::submodule_update(shell, config.link_to_code.clone())?;

let spinner = Spinner::new(MSG_PREPARING_CONFIG_SPINNER);
copy_configs(shell, &config.link_to_code, &chain_config.configs)?;

logger::note(MSG_SELECTED_CONFIG, logger::object_to_string(&chain_config));

let mut genesis_config = chain_config.get_genesis_config()?;
update_from_chain_config(&mut genesis_config, &chain_config);

// Copy ecosystem contracts
let mut contracts_config = config
.get_contracts_config()
.context(MSG_CHAIN_TXN_MISSING_CONTRACT_CONFIG)?;
contracts_config.l1.base_token_addr = chain_config.base_token.address;
spinner.finish();

let spinner = Spinner::new(MSG_BUILDING_CHAIN_REGISTRATION_TXNS_SPINNER);
let governor: String = config.get_wallets()?.governor.address.encode_hex_upper();

register_chain(
shell,
args.forge_args.clone(),
&config,
&chain_config,
&mut contracts_config,
args.l1_rpc_url.clone(),
Some(governor),
false,
)
.await?;

contracts_config.save_with_base_path(shell, &args.out)?;
spinner.finish();

let spinner = Spinner::new(MSG_WRITING_OUTPUT_FILES_SPINNER);
shell
.create_dir(&args.out)
.context(MSG_CHAIN_TXN_OUT_PATH_INVALID_ERR)?;

shell.copy_file(
config.link_to_code.join(REGISTER_CHAIN_TXNS_FILE_SRC),
args.out.join(REGISTER_CHAIN_TXNS_FILE_DST),
)?;

shell.copy_file(
config.link_to_code.join(SCRIPT_CONFIG_FILE_SRC),
args.out.join(SCRIPT_CONFIG_FILE_DST),
)?;
spinner.finish();

logger::success(MSG_CHAIN_TRANSACTIONS_BUILT);
Ok(())
}
Loading

0 comments on commit 73c0b7c

Please sign in to comment.