diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 430fd37..410a2f1 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -8,5 +8,5 @@ Please provide a summary of the changes and any backward incompatibilities. - [ ] I have read the [DCO][DCO] and ensured that these changes comply. - [ ] I assign this work under its [open source licensing][terms]. -[DCO]: licenses/DCO.txt -[terms]: licenses/COPYRIGHT.md +[DCO]: https://github.com/OffchainLabs/cargo-stylus/licenses/DCO.txt +[terms]: https://github.com/OffchainLabs/cargo-stylus/licenses/COPYRIGHT.md diff --git a/Cargo.lock b/Cargo.lock index 290c20b..0c3264a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -437,7 +437,7 @@ dependencies = [ [[package]] name = "cargo-stylus" -version = "0.1.8" +version = "0.2.0" dependencies = [ "alloy-json-abi", "brotli2", diff --git a/Cargo.toml b/Cargo.toml index 9e88372..a148a7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo-stylus" -version = "0.1.8" +version = "0.2.0" edition = "2021" authors = ["Offchain Labs"] license = "MIT OR Apache-2.0" diff --git a/src/check.rs b/src/check.rs index 649463d..f3dda61 100644 --- a/src/check.rs +++ b/src/check.rs @@ -63,7 +63,7 @@ impl std::fmt::Display for FileByteSize { pub async fn run_checks(cfg: CheckConfig) -> eyre::Result { let wasm_file_path: PathBuf = match &cfg.wasm_file_path { Some(path) => PathBuf::from_str(path).unwrap(), - None => project::build_project_to_wasm(BuildConfig { + None => project::build_project_dylib(BuildConfig { opt_level: project::OptLevel::default(), nightly: cfg.nightly, rebuild: true, @@ -72,14 +72,13 @@ pub async fn run_checks(cfg: CheckConfig) -> eyre::Result { }; println!("Reading WASM file at {}", wasm_file_path.display().grey()); - let (precompressed_bytes, deploy_ready_code) = - project::get_compressed_wasm_bytes(&wasm_file_path) - .map_err(|e| eyre!("failed to get compressed WASM bytes: {e}"))?; + let (precompressed_bytes, init_code) = project::compress_wasm(&wasm_file_path) + .map_err(|e| eyre!("failed to get compressed WASM bytes: {e}"))?; let precompressed_size = FileByteSize::new(precompressed_bytes.len() as u64); println!("Uncompressed WASM size: {precompressed_size}"); - let compressed_size = FileByteSize::new(deploy_ready_code.len() as u64); + let compressed_size = FileByteSize::new(init_code.len() as u64); println!("Compressed WASM size to be deployed onchain: {compressed_size}",); println!( @@ -110,7 +109,7 @@ pub async fn run_checks(cfg: CheckConfig) -> eyre::Result { expected_program_addr = get_contract_address(wallet.address(), nonce); } - check_can_activate(provider, &expected_program_addr, deploy_ready_code).await + check_can_activate(provider, &expected_program_addr, init_code).await } /// Checks if a program can be successfully activated onchain before it is deployed diff --git a/src/constants.rs b/src/constants.rs index c96c174..6df02eb 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -23,8 +23,12 @@ pub const ARBWASM_ACTIVATE_METHOD_HASH: &str = "58c780c2"; /// Target for compiled WASM folder in a Rust project pub const RUST_TARGET: &str = "wasm32-unknown-unknown"; -/// URL of the template repository to start a boilerplate Stylus project in Rust. -pub const GITHUB_TEMPLATE_REPOSITORY: &str = "https://github.com/OffchainLabs/stylus-hello-world"; +/// The default repo to clone when creating new projects +pub const GITHUB_TEMPLATE_REPO: &str = "https://github.com/OffchainLabs/stylus-hello-world"; + +/// The minimal entrypoint repo +pub const GITHUB_TEMPLATE_REPO_MINIMAL: &str = + "https://github.com/OffchainLabs/stylus-hello-world-minimal"; /// The error returned from a call onchain if a program is up to date and requires no activation. pub const PROGRAM_UP_TO_DATE_ERR: &str = "ProgramUpToDate"; diff --git a/src/deploy.rs b/src/deploy.rs index 486afd9..001a633 100644 --- a/src/deploy.rs +++ b/src/deploy.rs @@ -84,7 +84,7 @@ on the --mode flag under cargo stylus deploy --help"# } }; - // Whether or not to send the transactions to the endpoint. + // Whether to send the transactions to the endpoint. let dry_run = cfg.tx_sending_opts.dry_run; // If we are attempting to send a real transaction, we check if the deployer has any funds @@ -113,21 +113,21 @@ programs to Stylus chains here https://docs.arbitrum.io/stylus/stylus-quickstart if deploy { let wasm_file_path: PathBuf = match &cfg.check_cfg.wasm_file_path { Some(path) => PathBuf::from_str(path).unwrap(), - None => project::build_project_to_wasm(BuildConfig { + None => project::build_project_dylib(BuildConfig { opt_level: project::OptLevel::default(), nightly: cfg.check_cfg.nightly, rebuild: false, // The check step at the start of this command rebuilt. }) .map_err(|e| eyre!("could not build project to WASM: {e}"))?, }; - let (_, deploy_ready_code) = project::get_compressed_wasm_bytes(&wasm_file_path)?; + let (_, init_code) = project::compress_wasm(&wasm_file_path)?; println!(""); println!("{}", "====DEPLOYMENT====".grey()); println!( "Deploying program to address {}", to_checksum(&expected_program_addr, None).mint(), ); - let deployment_calldata = program_deployment_calldata(&deploy_ready_code); + let deployment_calldata = program_deployment_calldata(&init_code); // Output the tx data to a user's specified directory if desired. if let Some(tx_data_output_dir) = output_dir { diff --git a/src/new.rs b/src/new.rs index 254875a..6c6148c 100644 --- a/src/new.rs +++ b/src/new.rs @@ -1,79 +1,28 @@ // Copyright 2023, Offchain Labs, Inc. // For licensing, see https://github.com/OffchainLabs/cargo-stylus/blob/main/licenses/COPYRIGHT.md use eyre::{bail, eyre}; -use std::{ - env::current_dir, - io::Write, - path::PathBuf, - process::{Command, Stdio}, -}; +use std::{env::current_dir, path::PathBuf}; -use crate::{color::Color, constants::GITHUB_TEMPLATE_REPOSITORY}; +use crate::{ + color::Color, + constants::{GITHUB_TEMPLATE_REPO, GITHUB_TEMPLATE_REPO_MINIMAL}, + util, +}; -/// Initializes a new Stylus project in the current directory by git cloning -/// the stylus-hello-world template repository and renaming -/// it to the user's choosing. +/// Creates a new Stylus project in the current directory pub fn new_stylus_project(name: &str, minimal: bool) -> eyre::Result<()> { if name.is_empty() { bail!("cannot have an empty project name"); } let cwd: PathBuf = current_dir().map_err(|e| eyre!("could not get current dir: {e}"))?; - if minimal { - let output = Command::new("cargo") - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .arg("new") - .arg("--bin") - .arg(name) - .output() - .map_err(|e| eyre!("failed to execute cargo new: {e}"))?; - if !output.status.success() { - bail!("cargo new command failed"); - } - - let cargo_config_dir_path = cwd.join(name).join(".cargo"); - std::fs::create_dir_all(&cargo_config_dir_path) - .map_err(|e| eyre!("could not create .cargo folder in new project: {e}"))?; - let cargo_config_path = cargo_config_dir_path.join("config"); - let mut f = std::fs::OpenOptions::new() - .create(true) - .write(true) - .open(cargo_config_path) - .map_err(|e| eyre!("could not open config file: {e}"))?; - f.write_all(cargo_config().as_bytes()) - .map_err(|e| eyre!("could not write to file: {e}"))?; - f.flush() - .map_err(|e| eyre!("could not write to file: {e}"))?; - let main_path = cwd.join(name).join("src").join("main.rs"); - - // Overwrite the default main.rs file with the Stylus entrypoint. - let mut f = std::fs::OpenOptions::new() - .write(true) - .open(main_path) - .map_err(|e| eyre!("could not open main.rs file: {e}"))?; - f.write_all(basic_entrypoint().as_bytes()) - .map_err(|e| eyre!("could not write to file: {e}"))?; - f.flush() - .map_err(|e| eyre!("could not write to file: {e}"))?; - - // Overwrite the default Cargo.toml file. - let cargo_path = cwd.join(name).join("Cargo.toml"); - let mut f = std::fs::OpenOptions::new() - .write(true) - .open(cargo_path) - .map_err(|e| eyre!("could not open Cargo.toml file: {e}"))?; - f.write_all(minimal_cargo_toml(name).as_bytes()) - .map_err(|e| eyre!("could not write to file: {e}"))?; - f.flush() - .map_err(|e| eyre!("could not write to file: {e}"))?; - return Ok(()); - } - let output = Command::new("git") - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) + let repo = match minimal { + true => GITHUB_TEMPLATE_REPO_MINIMAL, + false => GITHUB_TEMPLATE_REPO, + }; + let output = util::new_command("git") .arg("clone") - .arg(GITHUB_TEMPLATE_REPOSITORY) + .arg(repo) .arg(name) .output() .map_err(|e| eyre!("failed to execute git clone: {e}"))?; @@ -88,60 +37,3 @@ pub fn new_stylus_project(name: &str, minimal: bool) -> eyre::Result<()> { ); Ok(()) } - -fn basic_entrypoint() -> &'static str { - r#"#![no_main] -#![no_std] -extern crate alloc; - -#[global_allocator] -static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; - -use alloc::vec::Vec; - -use stylus_sdk::stylus_proc::entrypoint; - -#[entrypoint] -fn user_main(input: Vec) -> Result, Vec> { - Ok(input) -} -"# -} - -fn cargo_config() -> &'static str { - r#"[build] -target = "wasm32-unknown-unknown" - -[target.wasm32-unknown-unknown] -rustflags = [ - "-C", "link-arg=-zstack-size=32768", -] - "# -} - -fn minimal_cargo_toml(name: &str) -> String { - format!( - r#"[package] -name = "{}" -version = "0.1.0" -edition = "2021" - -[dependencies] -stylus-sdk = "0.4.1" -wee_alloc = "0.4.5" - -[features] -export-abi = ["stylus-sdk/export-abi"] - -[profile.release] -codegen-units = 1 -strip = true -lto = true -panic = "abort" -opt-level = "s" - -[workspace] -"#, - name - ) -} diff --git a/src/project.rs b/src/project.rs index 689e28c..6fc75c7 100644 --- a/src/project.rs +++ b/src/project.rs @@ -1,19 +1,16 @@ // Copyright 2023, Offchain Labs, Inc. // For licensing, see https://github.com/OffchainLabs/cargo-stylus/blob/main/licenses/COPYRIGHT.md -use std::env::current_dir; -use std::io::Read; -use std::path::PathBuf; -use std::process::{Command, Stdio}; - -use brotli2::read::BrotliEncoder; -use bytesize::ByteSize; -use eyre::{bail, eyre}; use crate::constants::{MAX_PRECOMPRESSED_WASM_SIZE, MAX_PROGRAM_SIZE}; +use crate::util; use crate::{ color::Color, constants::{BROTLI_COMPRESSION_LEVEL, EOF_PREFIX, RUST_TARGET}, }; +use brotli2::read::BrotliEncoder; +use bytesize::ByteSize; +use eyre::{bail, eyre, Result}; +use std::{env::current_dir, io::Read, path::PathBuf}; #[derive(Default, PartialEq)] pub enum OptLevel { @@ -55,12 +52,11 @@ https://github.com/OffchainLabs/cargo-stylus/blob/main/OPTIMIZING_BINARIES.md"#) } /// Build a Rust project to WASM and return the path to the compiled WASM file. -pub fn build_project_to_wasm(cfg: BuildConfig) -> eyre::Result { +pub fn build_project_dylib(cfg: BuildConfig) -> Result { let cwd: PathBuf = current_dir().map_err(|e| eyre!("could not get current dir: {e}"))?; if cfg.rebuild { - let mut cmd = Command::new("cargo"); - cmd.stdout(Stdio::inherit()).stderr(Stdio::inherit()); + let mut cmd = util::new_command("cargo"); if cfg.nightly { cmd.arg("+nightly"); @@ -69,6 +65,7 @@ pub fn build_project_to_wasm(cfg: BuildConfig) -> eyre::Result { } cmd.arg("build"); + cmd.arg("--lib"); if cfg.nightly { cmd.arg("-Z"); @@ -84,7 +81,7 @@ pub fn build_project_to_wasm(cfg: BuildConfig) -> eyre::Result { let output = cmd .arg("--release") - .arg(format!("--target={}", RUST_TARGET)) + .arg(format!("--target={RUST_TARGET}")) .output() .map_err(|e| eyre!("failed to execute cargo build: {e}"))?; @@ -93,13 +90,17 @@ pub fn build_project_to_wasm(cfg: BuildConfig) -> eyre::Result { } } - let release_path = cwd.join("target").join(RUST_TARGET).join("release"); + let release_path = cwd + .join("target") + .join(RUST_TARGET) + .join("release") + .join("deps"); // Gets the files in the release folder. let release_files: Vec = std::fs::read_dir(&release_path) - .map_err(|e| eyre!("could not read release dir: {e}"))? - .filter(|r| r.is_ok()) - .map(|r| r.unwrap().path()) + .map_err(|e| eyre!("could not read deps dir: {e}"))? + .filter_map(|r| r.ok()) + .map(|r| r.path()) .filter(|r| r.is_file()) .collect(); @@ -113,7 +114,7 @@ pub fn build_project_to_wasm(cfg: BuildConfig) -> eyre::Result { }) .ok_or(BuildError::NoWasmFound { path: release_path })?; - if let Err(e) = get_compressed_wasm_bytes(&wasm_file_path) { + if let Err(e) = compress_wasm(&wasm_file_path) { if let Some(BuildError::MaxCompressedSizeExceeded { got, .. }) = e.downcast_ref() { match cfg.opt_level { OptLevel::S => { @@ -125,7 +126,7 @@ https://github.com/OffchainLabs/cargo-stylus/blob/main/OPTIMIZING_BINARIES.md"#, got.red(), ); // Attempt to build again with a bumped-up optimization level. - return build_project_to_wasm(BuildConfig { + return build_project_dylib(BuildConfig { opt_level: OptLevel::Z, nightly: cfg.nightly, rebuild: true, @@ -142,7 +143,7 @@ https://github.com/OffchainLabs/cargo-stylus/blob/main/OPTIMIZING_BINARIES.md"#, ); // Attempt to build again with the nightly flag enabled and extra optimizations // only available with nightly compilation. - return build_project_to_wasm(BuildConfig { + return build_project_dylib(BuildConfig { opt_level: OptLevel::Z, nightly: true, rebuild: true, @@ -162,7 +163,7 @@ https://github.com/OffchainLabs/cargo-stylus/blob/main/OPTIMIZING_BINARIES.md"#, } /// Reads a WASM file at a specified path and returns its brotli compressed bytes. -pub fn get_compressed_wasm_bytes(wasm_path: &PathBuf) -> eyre::Result<(Vec, Vec)> { +pub fn compress_wasm(wasm_path: &PathBuf) -> Result<(Vec, Vec)> { let wasm_file_bytes = std::fs::read(wasm_path).map_err(|e| { eyre!( "could not read WASM file at target path {}: {e}", @@ -172,13 +173,13 @@ pub fn get_compressed_wasm_bytes(wasm_path: &PathBuf) -> eyre::Result<(Vec, let wasm_bytes = wasmer::wat2wasm(&wasm_file_bytes) .map_err(|e| eyre!("could not parse wasm file bytes: {e}"))?; - let wasm_bytes = &*wasm_bytes; - let mut compressor = BrotliEncoder::new(wasm_bytes, BROTLI_COMPRESSION_LEVEL); + let mut compressor = BrotliEncoder::new(&*wasm_bytes, BROTLI_COMPRESSION_LEVEL); let mut compressed_bytes = vec![]; compressor .read_to_end(&mut compressed_bytes) .map_err(|e| eyre!("could not Brotli compress WASM bytes: {e}"))?; + let mut deploy_ready_code = hex::decode(EOF_PREFIX).unwrap(); deploy_ready_code.extend(compressed_bytes); diff --git a/src/util.rs b/src/util.rs index c650c14..bfdee39 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,7 +1,11 @@ // Copyright 2023, Offchain Labs, Inc. // For licensing, see https://github.com/OffchainLabs/cargo-stylus/blob/main/licenses/COPYRIGHT.md -use std::time::Duration; +use std::{ + ffi::OsStr, + process::{Command, Stdio}, + time::Duration, +}; use ethers::{prelude::*, providers::Provider}; use eyre::{eyre, Context, Result}; @@ -13,3 +17,9 @@ pub fn new_provider(url: &str) -> Result> { provider.set_interval(Duration::from_millis(250)); Ok(provider) } + +pub fn new_command>(program: S) -> Command { + let mut command = Command::new(program); + command.stdout(Stdio::inherit()).stderr(Stdio::inherit()); + command +}