diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index e15af8691c..41be49507b 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -89,11 +89,11 @@ Tools for smart contract developers - `id` — Generate the contract id for a given contract or asset - `info` — Access info about contracts - `init` — Initialize a Soroban contract project -- `inspect` — (Deprecated in favor of `contract info` subcommand) Inspect a WASM file listing contract functions, meta, etc +- `inspect` — (Deprecated, use `contract info`) Inspect a WASM file listing contract functions, meta, etc - `upload` — Install a WASM file to the ledger without creating a contract instance -- `install` — (Deprecated in favor of `contract upload` subcommand) Install a WASM file to the ledger without creating a contract instance +- `install` — (Deprecated, use `contract upload`) Install a WASM file to the ledger without creating a contract instance - `invoke` — Invoke a contract function -- `optimize` — Optimize a WASM file +- `optimize` — (Deprecated, use `build --optimize`) Optimize a WASM file - `read` — Print the current value of a contract-data ledger entry - `restore` — Restore an evicted value for a contract-data legder entry @@ -357,6 +357,7 @@ To view the commands that will be executed, without executing them, use the --pr - `--print-commands-only` — Print commands to build without executing them - `--meta ` — Add key-value to contract meta (adds the meta to the `contractmetav0` custom section) +- `--optimize` — Optimize the generated wasm ## `stellar contract extend` @@ -661,7 +662,7 @@ This command will create a Cargo workspace project and add a sample Stellar cont ## `stellar contract inspect` -(Deprecated in favor of `contract info` subcommand) Inspect a WASM file listing contract functions, meta, etc +(Deprecated, use `contract info`) Inspect a WASM file listing contract functions, meta, etc **Usage:** `stellar contract inspect [OPTIONS] --wasm ` @@ -713,7 +714,7 @@ Install a WASM file to the ledger without creating a contract instance ## `stellar contract install` -(Deprecated in favor of `contract upload` subcommand) Install a WASM file to the ledger without creating a contract instance +(Deprecated, use `contract upload`) Install a WASM file to the ledger without creating a contract instance **Usage:** `stellar contract install [OPTIONS] --source-account --wasm ` @@ -789,7 +790,7 @@ stellar contract invoke ... -- --help ## `stellar contract optimize` -Optimize a WASM file +(Deprecated, use `build --optimize`) Optimize a WASM file **Usage:** `stellar contract optimize [OPTIONS] --wasm ...` diff --git a/cmd/soroban-cli/src/commands/contract/build.rs b/cmd/soroban-cli/src/commands/contract/build.rs index 7dbb296781..440eef9220 100644 --- a/cmd/soroban-cli/src/commands/contract/build.rs +++ b/cmd/soroban-cli/src/commands/contract/build.rs @@ -17,7 +17,11 @@ use std::{ }; use stellar_xdr::curr::{Limited, Limits, ScMetaEntry, ScMetaV0, StringM, WriteXdr}; -use crate::{commands::global, print::Print}; +use crate::{ + commands::{contract::optimize, global}, + print::Print, + wasm, +}; /// Build a contract from source /// @@ -32,6 +36,7 @@ use crate::{commands::global, print::Print}; /// To view the commands that will be executed, without executing them, use the /// --print-commands-only option. #[derive(Parser, Debug, Clone)] +#[allow(clippy::struct_excessive_bools)] pub struct Cmd { /// Path to Cargo.toml #[arg(long)] @@ -41,12 +46,15 @@ pub struct Cmd { /// If omitted, all packages that build for crate-type cdylib are built. #[arg(long)] pub package: Option, + /// Build with the specified profile #[arg(long, default_value = "release")] pub profile: String, + /// Build with the list of features activated, space or comma separated #[arg(long, help_heading = "Features")] pub features: Option, + /// Build with the all features activated #[arg( long, @@ -55,9 +63,11 @@ pub struct Cmd { help_heading = "Features" )] pub all_features: bool, + /// Build with the default feature not activated #[arg(long, help_heading = "Features")] pub no_default_features: bool, + /// Directory to copy wasm files to /// /// If provided, wasm files can be found in the cargo target directory, and @@ -66,12 +76,18 @@ pub struct Cmd { /// If ommitted, wasm files are written only to the cargo target directory. #[arg(long)] pub out_dir: Option, + /// Print commands to build without executing them #[arg(long, conflicts_with = "out_dir", help_heading = "Other")] pub print_commands_only: bool, + /// Add key-value to contract meta (adds the meta to the `contractmetav0` custom section) #[arg(long, num_args=1, value_parser=parse_meta_arg, action=clap::ArgAction::Append, help_heading = "Metadata")] pub meta: Vec<(String, String)>, + + /// Optimize the generated wasm. + #[arg(long)] + pub optimize: bool, } fn parse_meta_arg(s: &str) -> Result<(String, String), Error> { @@ -89,34 +105,54 @@ fn parse_meta_arg(s: &str) -> Result<(String, String), Error> { pub enum Error { #[error(transparent)] Metadata(#[from] cargo_metadata::Error), + #[error(transparent)] CargoCmd(io::Error), + #[error("exit status {0}")] Exit(ExitStatus), + #[error("package {package} not found")] PackageNotFound { package: String }, + #[error("finding absolute path of Cargo.toml: {0}")] AbsolutePath(io::Error), + #[error("creating out directory: {0}")] CreatingOutDir(io::Error), + #[error("deleting existing artifact: {0}")] DeletingArtifact(io::Error), + #[error("copying wasm file: {0}")] CopyingWasmFile(io::Error), + #[error("getting the current directory: {0}")] GettingCurrentDir(io::Error), + #[error("retreiving CARGO_HOME: {0}")] CargoHome(io::Error), + #[error("reading wasm file: {0}")] ReadingWasmFile(io::Error), + #[error("writing wasm file: {0}")] WritingWasmFile(io::Error), + #[error("invalid meta entry: {0}")] MetaArg(String), + #[error("use rust 1.81 or 1.84+ to build contracts (got {0})")] RustVersion(String), + #[error(transparent)] Xdr(#[from] stellar_xdr::curr::Error), + + #[error(transparent)] + Optimize(#[from] optimize::Error), + + #[error(transparent)] + Wasm(#[from] wasm::Error), } const WASM_TARGET: &str = "wasm32v1-none"; @@ -202,7 +238,8 @@ impl Cmd { return Err(Error::Exit(status)); } - let file = format!("{}.wasm", p.name.replace('-', "_")); + let wasm_name = p.name.replace('-', "_"); + let file = format!("{wasm_name}.wasm"); let target_file_path = Path::new(target_dir) .join(&wasm_target) .join(&self.profile) @@ -219,7 +256,20 @@ impl Cmd { target_file_path }; - Self::print_build_summary(&print, &final_path)?; + let wasm_bytes = fs::read(&final_path).map_err(Error::ReadingWasmFile)?; + let mut optimized_wasm_bytes: Vec = Vec::new(); + + if self.optimize { + let mut path = final_path.clone(); + path.set_extension("optimized.wasm"); + optimize::optimize(true, vec![final_path.clone()], Some(path.clone()))?; + optimized_wasm_bytes = fs::read(&path).map_err(Error::ReadingWasmFile)?; + + fs::remove_file(&final_path).map_err(Error::DeletingArtifact)?; + fs::rename(&path, &final_path).map_err(Error::CopyingWasmFile)?; + } + + Self::print_build_summary(&print, &final_path, wasm_bytes, optimized_wasm_bytes); } } @@ -332,20 +382,41 @@ impl Cmd { Ok(buffer) } - fn print_build_summary(print: &Print, target_file_path: &PathBuf) -> Result<(), Error> { + fn print_build_summary( + print: &Print, + path: &Path, + wasm_bytes: Vec, + optimized_wasm_bytes: Vec, + ) { print.infoln("Build Summary:"); - let rel_target_file_path = target_file_path + + let rel_path = path .strip_prefix(env::current_dir().unwrap()) - .unwrap_or(target_file_path); - print.blankln(format!("Wasm File: {}", rel_target_file_path.display())); + .unwrap_or(path); + + let size = wasm_bytes.len(); + let optimized_size = optimized_wasm_bytes.len(); + + let size_description = if optimized_size > 0 { + format!("{optimized_size} bytes optimized (original size was {size} bytes)") + } else { + format!("{size} bytes") + }; - let wasm_bytes = fs::read(target_file_path).map_err(Error::ReadingWasmFile)?; + let bytes = if optimized_size > 0 { + &optimized_wasm_bytes + } else { + &wasm_bytes + }; print.blankln(format!( - "Wasm Hash: {}", - hex::encode(Sha256::digest(&wasm_bytes)) + "Wasm File: {path} ({size_description})", + path = rel_path.display() )); + print.blankln(format!("Wasm Hash: {}", hex::encode(Sha256::digest(bytes)))); + print.blankln(format!("Wasm Size: {size_description}")); + let parser = wasmparser::Parser::new(0); let export_names: Vec<&str> = parser .parse_all(&wasm_bytes) @@ -363,6 +434,7 @@ impl Cmd { .map(|export| export.name) .sorted() .collect(); + if export_names.is_empty() { print.blankln("Exported Functions: None found"); } else { @@ -371,9 +443,8 @@ impl Cmd { print.blankln(format!(" • {name}")); } } - print.checkln("Build Complete"); - Ok(()) + print.checkln("Build Complete\n"); } } diff --git a/cmd/soroban-cli/src/commands/contract/mod.rs b/cmd/soroban-cli/src/commands/contract/mod.rs index dd642755ad..0c2bd968ae 100644 --- a/cmd/soroban-cli/src/commands/contract/mod.rs +++ b/cmd/soroban-cli/src/commands/contract/mod.rs @@ -61,14 +61,14 @@ pub enum Cmd { /// be overwritten unless `--overwrite` is passed. Init(init::Cmd), - /// (Deprecated in favor of `contract info` subcommand) Inspect a WASM file listing contract functions, meta, etc + /// (Deprecated, use `contract info`) Inspect a WASM file listing contract functions, meta, etc #[command(display_order = 100)] Inspect(inspect::Cmd), /// Install a WASM file to the ledger without creating a contract instance Upload(upload::Cmd), - /// (Deprecated in favor of `contract upload` subcommand) Install a WASM file to the ledger without creating a contract instance + /// (Deprecated, use `contract upload`) Install a WASM file to the ledger without creating a contract instance Install(upload::Cmd), /// Invoke a contract function @@ -81,7 +81,7 @@ pub enum Cmd { /// stellar contract invoke ... -- --help Invoke(invoke::Cmd), - /// Optimize a WASM file + /// (Deprecated, use `build --optimize`) Optimize a WASM file Optimize(optimize::Cmd), /// Print the current value of a contract-data ledger entry @@ -165,7 +165,7 @@ impl Cmd { } Cmd::Upload(upload) => upload.run(global_args).await?, Cmd::Invoke(invoke) => invoke.run(global_args).await?, - Cmd::Optimize(optimize) => optimize.run()?, + Cmd::Optimize(optimize) => optimize.run(global_args)?, Cmd::Fetch(fetch) => fetch.run().await?, Cmd::Read(read) => read.run().await?, Cmd::Restore(restore) => restore.run().await?, diff --git a/cmd/soroban-cli/src/commands/contract/optimize.rs b/cmd/soroban-cli/src/commands/contract/optimize.rs index e8d21cda44..5e2310afa1 100644 --- a/cmd/soroban-cli/src/commands/contract/optimize.rs +++ b/cmd/soroban-cli/src/commands/contract/optimize.rs @@ -3,6 +3,8 @@ use std::{fmt::Debug, path::PathBuf}; #[cfg(feature = "additional-libs")] use wasm_opt::{Feature, OptimizationError, OptimizationOptions}; +#[cfg(feature = "additional-libs")] +use crate::commands::global; use crate::wasm; #[derive(Parser, Debug, Clone)] @@ -36,60 +38,77 @@ pub enum Error { impl Cmd { #[cfg(not(feature = "additional-libs"))] - pub fn run(&self) -> Result<(), Error> { + pub fn run(&self, _global_args: &global::Args) -> Result<(), Error> { Err(Error::Install) } #[cfg(feature = "additional-libs")] - pub fn run(&self) -> Result<(), Error> { - if self.wasm.len() > 1 && self.wasm_out.is_some() { - return Err(Error::MultipleFilesOutput); - } + pub fn run(&self, global_args: &global::Args) -> Result<(), Error> { + use crate::print::Print; + + let print = Print::new(global_args.quiet); + print + .warnln("`stellar contract optimize` is deprecated and will be removed in the future. Use `stellar contract build --optimize` instead."); + + optimize(false, self.wasm.clone(), self.wasm_out.clone()) + } +} + +#[cfg(feature = "additional-libs")] +pub fn optimize( + quiet: bool, + wasm: Vec, + wasm_out: Option, +) -> Result<(), Error> { + if wasm.len() > 1 && wasm_out.is_some() { + return Err(Error::MultipleFilesOutput); + } - for wasm_path in &self.wasm { - let wasm_arg = wasm::Args { - wasm: wasm_path.into(), - }; - let wasm_size = wasm_arg.len()?; + for wasm_path in &wasm { + let wasm_arg = wasm::Args { + wasm: wasm_path.into(), + }; + if !quiet { println!( - "Reading: {} ({} bytes)", - wasm_arg.wasm.to_string_lossy(), - wasm_size + "Reading: {path} ({wasm_size} bytes)", + path = wasm_arg.wasm.to_string_lossy(), + wasm_size = wasm_arg.len()? ); + } - let wasm_out = self.wasm_out.clone().unwrap_or_else(|| { - let mut wasm_out = wasm_arg.wasm.clone(); - wasm_out.set_extension("optimized.wasm"); - wasm_out - }); - - let mut options = OptimizationOptions::new_optimize_for_size_aggressively(); - options.converge = true; - - // Explicitly set to MVP + sign-ext + mutable-globals, which happens to - // also be the default featureset, but just to be extra clear we set it - // explicitly. - // - // Formerly Soroban supported only the MVP feature set, but Rust 1.70 as - // well as Clang generate code with sign-ext + mutable-globals enabled, - // so Soroban has taken a change to support them also. - options.mvp_features_only(); - options.enable_feature(Feature::MutableGlobals); - options.enable_feature(Feature::SignExt); - - options - .run(&wasm_arg.wasm, &wasm_out) - .map_err(Error::OptimizationError)?; - - let wasm_out_size = wasm::len(&wasm_out)?; + let wasm_out = wasm_out.clone().unwrap_or_else(|| { + let mut wasm_out = wasm_arg.wasm.clone(); + wasm_out.set_extension("optimized.wasm"); + wasm_out + }); + + let mut options = OptimizationOptions::new_optimize_for_size_aggressively(); + options.converge = true; + + // Explicitly set to MVP + sign-ext + mutable-globals, which happens to + // also be the default featureset, but just to be extra clear we set it + // explicitly. + // + // Formerly Soroban supported only the MVP feature set, but Rust 1.70 as + // well as Clang generate code with sign-ext + mutable-globals enabled, + // so Soroban has taken a change to support them also. + options.mvp_features_only(); + options.enable_feature(Feature::MutableGlobals); + options.enable_feature(Feature::SignExt); + + options + .run(&wasm_arg.wasm, &wasm_out) + .map_err(Error::OptimizationError)?; + + if !quiet { println!( - "Optimized: {} ({} bytes)", - wasm_out.to_string_lossy(), - wasm_out_size + "Optimized: {path} ({size} bytes)", + path = wasm_out.to_string_lossy(), + size = wasm::len(&wasm_out)? ); } - - Ok(()) } + + Ok(()) } diff --git a/cmd/soroban-cli/src/print.rs b/cmd/soroban-cli/src/print.rs index eeea11ebb0..c947832919 100644 --- a/cmd/soroban-cli/src/print.rs +++ b/cmd/soroban-cli/src/print.rs @@ -116,6 +116,6 @@ create_print_functions!(exclaim, exclaimln, "❗️"); create_print_functions!(arrow, arrowln, "➡️"); create_print_functions!(log, logln, "📔"); create_print_functions!(event, eventln, "📅"); -create_print_functions!(blank, blankln, " "); +create_print_functions!(blank, blankln, " "); create_print_functions!(gear, gearln, "⚙️"); create_print_functions!(dir, dirln, "📁"); diff --git a/licenses.rb b/licenses.rb deleted file mode 100644 index 4cc6def02a..0000000000 --- a/licenses.rb +++ /dev/null @@ -1,5 +0,0 @@ -require "json" - -metadata = JSON.parse(`cargo metadata --format-version 1 --frozen`) -packages = metadata["packages"] -puts JSON.pretty_generate(packages)