Skip to content

zksolc compiler integration (MVP) #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 47 commits into from
May 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
8f9b388
Add clippy lints
ilitteri May 12, 2023
1f7f38e
Re-export `ethers-rs`
ilitteri May 12, 2023
d58321a
Add simple CLI for improving development
ilitteri May 12, 2023
94a8753
Update imports
ilitteri May 12, 2023
8a6c44c
Remove `unwrap`s
ilitteri May 12, 2023
d727e45
Add Makefile
ilitteri May 12, 2023
1c8ce09
Fix `README.md`
ilitteri May 12, 2023
51f4edc
Add serde_json as dependency
IAvecilla May 15, 2023
0c5bbc8
Add solc and zksolc compiler binaries
IAvecilla May 15, 2023
e895186
Add new compile module to use zkSolc as compiler
IAvecilla May 15, 2023
88e95b6
Delete compiler binaries from repo
IAvecilla May 15, 2023
7819dfe
Add compilation output struct for serialization
IAvecilla May 16, 2023
e94969b
Add solc and zksolc compiler binaries
IAvecilla May 16, 2023
0e898e2
Update Makefile
ilitteri May 16, 2023
c12b6de
Add `compile` command
ilitteri May 16, 2023
16845eb
cargo fmt
ilitteri May 16, 2023
eda717e
Refactor `ZKProject::compile_zk`
ilitteri May 16, 2023
bb6ef73
`solc` bin is no longer needed
ilitteri May 16, 2023
6430b34
Rename
ilitteri May 16, 2023
d7f8e35
`compile` cmd now returns the compilation output
ilitteri May 16, 2023
fc996c0
Refactor `compile` cmd
ilitteri May 16, 2023
0524036
cargo fmt
ilitteri May 16, 2023
d03d455
Add documentation for `compile` cmd
ilitteri May 17, 2023
5902125
Revert "`solc` bin is no longer needed"
IAvecilla May 17, 2023
c23c6e9
Update deps
ilitteri May 17, 2023
e15f4eb
Change solc compiler path
IAvecilla May 17, 2023
8f50862
Add test
ilitteri May 17, 2023
3061e63
cargo fmt
ilitteri May 17, 2023
2bdac0a
Add struct to serialize abi and bytecodes output compilation
IAvecilla May 17, 2023
85d051e
Add test for compiling with files
ilitteri May 17, 2023
5064b55
Merge branch 'zksolc_compiler_integration' of github.com:lambdaclass/…
ilitteri May 17, 2023
ecc1618
Get both abi and bin when compiling
ilitteri May 17, 2023
c0cd01c
Finish `ContractOuput`
ilitteri May 17, 2023
6b9a6a6
cargo fmt
ilitteri May 17, 2023
8b16cce
Change type of factory_deps field in to serialize
IAvecilla May 17, 2023
6693270
Fix typo in serde-json version dependency
IAvecilla May 18, 2023
9163902
Fix abi compilation output serialization
IAvecilla May 18, 2023
d210f8f
Add support for multiple combined-json arguments in CLI
IAvecilla May 18, 2023
acffa58
Remove empty file
ilitteri May 18, 2023
9e5db76
Update `compile`'s `mod.rs`
ilitteri May 18, 2023
4af7226
Handle errors
ilitteri May 18, 2023
e11592a
Wrap ethers project in ZKProject
IAvecilla May 18, 2023
b9d0a12
Merge branch 'main' into zksolc_compiler_integration
IAvecilla May 19, 2023
b7dfe2d
Fix fmt
IAvecilla May 19, 2023
c5e0eef
Add solc and zksolc path as constants
IAvecilla May 19, 2023
b311876
`ZKSProvider` trait + `zks_estimateFee` & `zks_getTestnetPaymaster` m…
ilitteri May 19, 2023
8a9ff99
Update `Cargo.lock`
ilitteri May 22, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
396 changes: 114 additions & 282 deletions Cargo.lock

Large diffs are not rendered by default.

17 changes: 14 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,21 @@ edition = "2021"

[dependencies]
ethers = { version = "2.0.4", features = ["rustls"] }
tokio = { version = "1", features = ["macros"] }
hex = "0.4"
eyre = "0.6"
clap = { version = "4.2.7", features = ["derive"] }

# Async
tokio = { version = "1", features = ["macros", "process"] }
async-trait = "0.1.68"

# Serialization
serde = "1.0.163"
serde_json = { version = "1" }
hex = "0.4"

# Error handling
eyre = "0.6" # CLI error handling
thiserror = "1.0.40" # Library error handling

# Logging
log = "0.4"
env_logger = "0.10"
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
cli:
cargo install --path .

format:
cargo fmt --all

clippy:
cargo clippy --all-targets --all-features -- -D warnings
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,44 @@ Pays `AMOUNT` from `SENDER_ADDRESS` to `RECEIVER_ADDRESS` signing the transactio
```
zksync-web3-cli pay --amount <AMOUNT_TO_TRANSFER> --from <SENDER_ADDRESS> --to <RECEIVER_ADDRESS> --private-key <SENDER_PRIVATE_KEY>
```

#### `zksync-web3-cli compile`

> This command is a wrapper for the zksolc compiler.

Compiles the contract located in `PATH_TO_CONTRACT` using the zksolc compiler.

```
zksync-web3-cli compile --solc <PATH_TO_SOLC> --standard-json -- <PATH_TO_CONTRACT>
```

##### Status (for full compatibility)

| Flags | Description | Supported | State |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ----- |
| `--disable-solc-optimizer` | Disable the `solc` optimizer. Use it if your project uses the `MSIZE` instruction, or in other cases. Beware that it will prevent libraries from being inlined | ❌ | ❌ |
| `--force-evmla` | Forcibly switch to the EVM legacy assembly pipeline. It is useful for older revisions of `solc` 0.8, where Yul was considered highly experimental and contained more bugs than today | ❌ | ❌ |
| `-h, --help` | Prints help information | ✅ | ✅ |
| `--system-mode` | Enable the system contract compilation mode. In this mode zkEVM extensions are enabled. For example, calls to addresses `0xFFFF` and below are substituted by special zkEVM instructions. In the Yul mode, the `verbatim_*` instruction family is available | ❌ | ❌ |
| `--llvm-debug-logging` | Set the debug-logging option in LLVM. Only for testing and debugging | ❌ | ❌ |
| `--llvm-ir` | Switch to the LLVM IR mode. Only one input LLVM IR file is allowed. Cannot be used with the combined and standard JSON modes | ❌ | ❌ |
| `--llvm-verify-each` | Set the verify-each option in LLVM. Only for testing and debugging | ❌ | ❌ |
| `--asm` | Output zkEVM assembly of the contracts | ❌ | ❌ |
| `--bin` | Output zkEVM bytecode of the contracts | ❌ | ❌ |
| `--overwrite` | Overwrite existing files (used together with -o) | ❌ | ❌ |
| `--standard-json` | Switch to standard JSON input/output mode. Read from stdin, write the result to stdout. This is the default used by the hardhat plugin | ❌ | 🏗 |
| `--version` | Print the version and exit | ❌ | ❌ |
| `--yul` | Switch to the Yul mode. Only one input Yul file is allowed. Cannot be used with the combined and standard JSON modes | ❌ | ❌ |

| Options | Description | Supported | State |
| --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ----- |
| `--allow-paths <allow-paths>` | Allow a given path for imports. A list of paths can be supplied by separating them with a comma. Passed to `solc` without changes | ❌ | ❌ |
| `--base-path <base-path>` | Set the given path as the root of the source tree instead of the root of the filesystem. Passed to `solc` without changes | ❌ | ❌ |
| `--combined-json <combined-json>` | Output a single JSON document containing the specified information. Available arguments: `abi`, `hashes`, `metadata`, `devdoc`, `userdoc`, `storage-layout`, `ast`, `asm`, `bin`, `bin-runtime` | ✅ | ✅ |
| `--debug-output-dir <debug-output-directory>` | Dump all IRs to files in the specified directory. Only for testing and debugging | ❌ | ❌ |
| `--include-path <include-paths>...` | Make an additional source directory available to the default import callback. Can be used multiple times. Can only be used if the base path has a non-empty value. Passed to `solc` without changes | ❌ | ❌ |
| `-l, --libraries <libraries>...` | Specify addresses of deployable libraries. Syntax: `<libraryName>=<address> [, or whitespace] ...`. Addresses are interpreted as hexadecimal strings prefixed with `0x` | ❌ | ❌ |
| `--metadata-hash <metadata-hash>` | Set the metadata hash mode. The only supported value is `none` that disables appending the metadata hash. Is enabled by default | ❌ | ❌ |
| `-O, --optimization <optimization>` | Set the optimization parameter -O\[0 \| 1 \| 2 \| 3 \| s \| z\]. Use `3` for best performance and `z` for minimal size | ❌ | ❌ |
| `-o, --output-dir <output-directory>` | Create one file per component and contract/file at the specified directory, if given | ❌ | ❌ |
| `--solc <solc>` | Specify the path to the `solc` executable. By default, the one in `${PATH}` is used. Yul mode: `solc` is used for source code validation, as `zksolc` itself assumes that the input Yul is valid. LLVM IR mode: `solc` is unused | ✅ | 🏗 |
4 changes: 2 additions & 2 deletions src/cli/commands/call.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::cli::{commands::CHAIN_ID, ZKSyncWeb3Config};
use crate::cli::{commands::L1_CHAIN_ID, ZKSyncWeb3Config};
use crate::{
prelude::{k256::ecdsa::SigningKey, MiddlewareBuilder, SignerMiddleware},
providers::{Middleware, Provider},
Expand Down Expand Up @@ -49,7 +49,7 @@ pub(crate) async fn run(args: Call, config: ZKSyncWeb3Config) -> eyre::Result<()
.interval(std::time::Duration::from_millis(10));
if let Some(pk) = args.private_key {
let mut signer = pk.parse::<Wallet<SigningKey>>()?;
signer = Wallet::with_chain_id(signer, CHAIN_ID);
signer = Wallet::with_chain_id(signer, L1_CHAIN_ID);
let provider = provider.clone().with_signer(signer);
provider.fill_transaction(&mut transaction, None).await?;
let response: TransactionReceipt =
Expand Down
63 changes: 63 additions & 0 deletions src/cli/commands/compile.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use clap::Parser;
use std::path::PathBuf;

use crate::compile::{constants, output::ZKCompilationOutput};

#[derive(Parser)]
pub struct CompileArgs {
#[clap(long, name = "PATH_TO_SOLC")]
pub solc: Option<PathBuf>,
#[clap(long, name = "COMBINED_JSON")]
pub combined_json: Option<String>,
#[clap(long, action)]
pub standard_json: bool,
}

pub(crate) fn run(args: CompileArgs) -> eyre::Result<ZKCompilationOutput> {
let mut command = &mut std::process::Command::new(constants::ZK_SOLC_PATH);
if let Some(solc) = args.solc {
command = command.arg("--solc").arg(solc);
} else if let Ok(solc) = std::env::var("SOLC_PATH") {
command = command.arg("--solc").arg(solc);
} else {
eyre::bail!("no solc path provided");
}

const VALID_COMBINED_JSON_ARGS: [&str; 10] = [
"abi",
"hashes",
"metadata",
"devdoc",
"userdoc",
"storage-layout",
"ast",
"asm",
"bin",
"bin-runtime",
];

if let Some(combined_json_arg) = args.combined_json {
let valid_args = combined_json_arg
.split(',')
.all(|arg| VALID_COMBINED_JSON_ARGS.contains(&arg));
if !valid_args {
eyre::bail!("Invalid combined-json argument: {combined_json_arg}");
}
command = command.arg("--combined-json").arg(combined_json_arg);
}

if args.standard_json {
command = command.arg("--standard-json");
}

command = command
.arg("--")
.arg("src/compile/test_contracts/test/src/Test.sol");

let command_output = command.output()?;
let compilation_output: ZKCompilationOutput = serde_json::from_slice(&command_output.stdout)?;

log::info!("{compilation_output:?}");

Ok(compilation_output)
}
4 changes: 2 additions & 2 deletions src/cli/commands/deploy.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::cli::{commands::CHAIN_ID, ZKSyncWeb3Config};
use crate::cli::{commands::L1_CHAIN_ID, ZKSyncWeb3Config};
use crate::{
prelude::{k256::ecdsa::SigningKey, ContractFactory, SignerMiddleware},
providers::Provider,
Expand Down Expand Up @@ -32,7 +32,7 @@ pub(crate) async fn run(args: Deploy, config: ZKSyncWeb3Config) -> eyre::Result<
.clone();
let (abi, bytecode, _) = contract.into_parts();
let mut wallet = args.private_key.parse::<Wallet<SigningKey>>()?;
wallet = Wallet::with_chain_id(wallet, CHAIN_ID);
wallet = Wallet::with_chain_id(wallet, L1_CHAIN_ID);
let provider = Provider::try_from(format!(
"http://{host}:{port}",
host = config.host,
Expand Down
6 changes: 5 additions & 1 deletion src/cli/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ pub(crate) use account_balance::AccountBalance;
pub(crate) mod pay;
pub(crate) use pay::Pay;

pub(crate) mod compile;
pub(crate) use compile::CompileArgs;

// It is set so that the transaction is replay-protected (EIP-155)
// https://era.zksync.io/docs/api/hardhat/testing.html#connect-wallet-to-local-nodes
const CHAIN_ID: u64 = 9;
const L1_CHAIN_ID: u64 = 9;
const L2_CHAIN_ID: u64 = 270;
42 changes: 35 additions & 7 deletions src/cli/commands/pay.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::cli::ZKSyncWeb3Config;
use crate::zks_provider::ZKSProvider;
use crate::{
prelude::{k256::ecdsa::SigningKey, MiddlewareBuilder, SignerMiddleware},
providers::{Middleware, Provider},
Expand All @@ -11,7 +12,8 @@ use crate::{
use clap::Args;
use eyre::ContextCompat;

use super::CHAIN_ID;
use super::L1_CHAIN_ID;
use super::L2_CHAIN_ID;

#[derive(Args)]
pub(crate) struct Pay {
Expand All @@ -26,27 +28,53 @@ pub(crate) struct Pay {
}

pub(crate) async fn run(args: Pay, config: ZKSyncWeb3Config) -> eyre::Result<()> {
let signer = Wallet::with_chain_id(args.private_key, CHAIN_ID);
let signer = Wallet::with_chain_id(args.private_key, L2_CHAIN_ID);
let provider = Provider::try_from(format!(
"http://{host}:{port}",
host = config.host,
port = config.port
))?
.interval(std::time::Duration::from_millis(10))
.with_signer(signer);
.interval(std::time::Duration::from_millis(10));
let signer_middleware = provider.clone().with_signer(signer);

let payment_request = Eip1559TransactionRequest::new()
let mut payment_request = Eip1559TransactionRequest::new()
.from(args.from)
.to(args.to)
.value(args.amount);

// Pre-fill transaction
let fee = provider.estimate_fee(payment_request.clone()).await?;
payment_request = payment_request.max_priority_fee_per_gas(fee.max_priority_fee_per_gas);
payment_request = payment_request.max_fee_per_gas(fee.max_fee_per_gas);

let mut transaction: TypedTransaction = payment_request.into();
provider.fill_transaction(&mut transaction, None).await?;
log::debug!("Transaction request: {:?}", transaction);

log::debug!(
"Sender's balance before paying: {:?}",
provider.get_balance(args.from, None).await?
);
log::debug!(
"Receiver's balance before getting payed: {:?}",
provider.get_balance(args.to, None).await?
);

let payment_response: TransactionReceipt =
SignerMiddleware::send_transaction(&provider, transaction, None)
SignerMiddleware::send_transaction(&signer_middleware, transaction, None)
.await?
.await?
.context("No pending transaction")?;
log::info!("{:?}", payment_response);

log::info!("{:#?}", payment_response.transaction_hash);

log::debug!(
"Sender's balance after paying: {:?}",
provider.get_balance(args.from, None).await?
);
log::debug!(
"Receiver's balance after getting payed: {:?}",
provider.get_balance(args.to, None).await?
);
Ok(())
}
10 changes: 7 additions & 3 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
pub(crate) mod commands;
use clap::{command, Args, Parser, Subcommand};
use commands::{
account_balance, call, deploy, get_contract, get_transaction, pay, AccountBalance, Call,
Deploy, GetContract, GetTransaction, Pay,
account_balance, call, compile, deploy, get_contract, get_transaction, pay, AccountBalance,
Call, CompileArgs, Deploy, GetContract, GetTransaction, Pay,
};

const VERSION_STRING: &str = env!("CARGO_PKG_VERSION");
pub const VERSION_STRING: &str = env!("CARGO_PKG_VERSION");

#[derive(Parser)]
#[command(name="zksync-web3-cli", author, version=VERSION_STRING, about, long_about = None)]
Expand All @@ -32,6 +32,7 @@ enum ZKSyncWeb3Command {
GetTransaction(GetTransaction),
Balance(AccountBalance),
Pay(Pay),
Compile(CompileArgs),
}

pub async fn start() -> eyre::Result<()> {
Expand All @@ -43,6 +44,9 @@ pub async fn start() -> eyre::Result<()> {
ZKSyncWeb3Command::GetTransaction(args) => get_transaction::run(args, config).await?,
ZKSyncWeb3Command::Balance(args) => account_balance::run(args, config).await?,
ZKSyncWeb3Command::Pay(args) => pay::run(args, config).await?,
ZKSyncWeb3Command::Compile(args) => {
let _ = compile::run(args)?;
}
};

Ok(())
Expand Down
2 changes: 2 additions & 0 deletions src/compile/constants.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub const SOLC_PATH: &str = "src/compile/solc-macos";
pub const ZK_SOLC_PATH: &str = "src/compile/zksolc";
5 changes: 5 additions & 0 deletions src/compile/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#[derive(thiserror::Error, Debug)]
pub enum ZKCompilerError {
#[error("Compilation error: {0}")]
CompilationError(String),
}
4 changes: 4 additions & 0 deletions src/compile/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub mod constants;
pub mod errors;
pub mod output;
pub mod project;
58 changes: 58 additions & 0 deletions src/compile/output.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use std::collections::HashMap;

use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug)]
pub struct ContractOutput {
#[serde(skip_serializing_if = "Option::is_none")]
abi: Option<Vec<ContractFunctionOutput>>,
#[serde(skip_serializing_if = "Option::is_none")]
bin: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
metadata: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
devdoc: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
userdoc: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", rename = "kebab-case")]
storage_layout: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
ast: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
asm: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", rename = "kebab-case")]
bin_runtime: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
hashes: Option<HashMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
factory_deps: Option<HashMap<String, String>>,
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct FunctionArgsTypesOutput {
pub internal_type: String,
pub name: String,
#[serde(rename = "type")]
pub sol_type: String,
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct ContractFunctionOutput {
pub inputs: Vec<FunctionArgsTypesOutput>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub outputs: Option<Vec<FunctionArgsTypesOutput>>,
pub state_mutability: String,
#[serde(rename = "type")]
pub sol_struct_type: String,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct ZKCompilationOutput {
pub contracts: HashMap<String, ContractOutput>,
pub version: String,
pub zk_version: String,
}
Loading