From f5fe3faa711b0589979fff9399e63b0a717391bc Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Wed, 14 Jan 2026 19:22:03 +0100 Subject: [PATCH] Add default values to compiler arguments (#451) ## Description Adds default values to the compiler arguments below when parsed by `clap`. This does not change previous user-facing behavior as these defaults were implemented using different logic, but improves the UX and the visibility of the exact defaults used. **Example output from `resolc --help`:** (Only affected arguments are shown here, and some of their descriptions have been omitted here.) ``` Options: -O, --optimization Set the optimization parameter -O[0 | 1 | 2 | 3 | s | z]. Use `3` for best performance and `z` for minimal size [default: z] --metadata-hash Set the metadata hash type. Available types: `none`, `ipfs`, `keccak256` [default: keccak256] --heap-size The emulated EVM linear heap memory static buffer size in bytes. ... [default: 131072] --stack-size The contracts total stack size in bytes. ... [default: 131072] ``` ## Resolved Issues Closes #446 --- .../src/optimizer/settings/mod.rs | 23 +--- crates/resolc/src/resolc/arguments.rs | 65 ++++++---- crates/resolc/src/resolc/main.rs | 31 ++--- crates/resolc/src/tests/cli/standard_json.rs | 112 +++++++++++++++++- 4 files changed, 166 insertions(+), 65 deletions(-) diff --git a/crates/llvm-context/src/optimizer/settings/mod.rs b/crates/llvm-context/src/optimizer/settings/mod.rs index 6ef642f..468d9b5 100644 --- a/crates/llvm-context/src/optimizer/settings/mod.rs +++ b/crates/llvm-context/src/optimizer/settings/mod.rs @@ -64,14 +64,7 @@ impl Settings { /// Creates settings from a CLI optimization parameter. pub fn try_from_cli(value: char) -> anyhow::Result { Ok(match value { - '0' => Self::new( - // The middle-end optimization level. - inkwell::OptimizationLevel::None, - // The middle-end size optimization level. - SizeLevel::Zero, - // The back-end optimization level. - inkwell::OptimizationLevel::None, - ), + '0' => Self::none(), '1' => Self::new( inkwell::OptimizationLevel::Less, SizeLevel::Zero, @@ -84,23 +77,14 @@ impl Settings { // The back-end does not currently distinguish between O1, O2, and O3. inkwell::OptimizationLevel::Default, ), - '3' => Self::new( - inkwell::OptimizationLevel::Aggressive, - SizeLevel::Zero, - inkwell::OptimizationLevel::Aggressive, - ), + '3' => Self::cycles(), 's' => Self::new( // The middle-end optimization level is ignored when SizeLevel is set. inkwell::OptimizationLevel::Default, SizeLevel::S, inkwell::OptimizationLevel::Aggressive, ), - 'z' => Self::new( - // The middle-end optimization level is ignored when SizeLevel is set. - inkwell::OptimizationLevel::Default, - SizeLevel::Z, - inkwell::OptimizationLevel::Aggressive, - ), + 'z' => Self::size(), char => anyhow::bail!("Unexpected optimization option '{}'", char), }) } @@ -126,6 +110,7 @@ impl Settings { /// Returns the settings for the optimal size. pub fn size() -> Self { Self::new( + // The middle-end optimization level is ignored when SizeLevel is set. inkwell::OptimizationLevel::Default, SizeLevel::Z, inkwell::OptimizationLevel::Aggressive, diff --git a/crates/resolc/src/resolc/arguments.rs b/crates/resolc/src/resolc/arguments.rs index 58fc161..047b098 100644 --- a/crates/resolc/src/resolc/arguments.rs +++ b/crates/resolc/src/resolc/arguments.rs @@ -4,10 +4,12 @@ use std::collections::BTreeSet; use std::path::Path; use std::path::PathBuf; -use clap::Parser; +use clap::{parser::ValueSource, ArgMatches, CommandFactory, Parser}; use path_slash::PathExt; use revive_common::MetadataHash; -use revive_solc_json_interface::SolcStandardJsonOutputError; +use revive_solc_json_interface::{ + PolkaVMDefaultHeapMemorySize, PolkaVMDefaultStackMemorySize, SolcStandardJsonOutputError, +}; /// Compiles the provided Solidity input files (or use the standard input if no files /// are given or "-" is specified as a file name). Outputs the components based on the @@ -57,8 +59,8 @@ pub struct Arguments { /// Set the optimization parameter -O[0 | 1 | 2 | 3 | s | z]. /// Use `3` for best performance and `z` for minimal size. - #[arg(short = 'O', long = "optimization")] - pub optimization: Option, + #[arg(short = 'O', long = "optimization", default_value = "z")] + pub optimization: char, /// Disable the `solc` optimizer. /// Use it if your project uses the `MSIZE` instruction, or in other cases. @@ -116,9 +118,8 @@ pub struct Arguments { /// Set the metadata hash type. /// Available types: `none`, `ipfs`, `keccak256`. - /// The default is `keccak256`. - #[arg(long)] - pub metadata_hash: Option, + #[arg(long, default_value_t = MetadataHash::Keccak256)] + pub metadata_hash: MetadataHash, /// Output PolkaVM assembly of the contracts. #[arg(long = "asm")] @@ -185,9 +186,9 @@ pub struct Arguments { /// You are incentiviced to keep this value as small as possible: /// 1.Increasing the heap size will increase startup costs. /// 2.The heap size contributes to the total memory size a contract can use, - /// which includes the contracts code size - #[arg(long = "heap-size")] - pub heap_size: Option, + /// which includes the contracts code size. + #[arg(long = "heap-size", default_value_t = PolkaVMDefaultHeapMemorySize)] + pub heap_size: u32, /// The contracts total stack size in bytes. /// @@ -198,16 +199,17 @@ pub struct Arguments { /// eventually revert execution at runtime! /// /// You are incentiviced to keep this value as small as possible: - /// 1.Increasing the heap size will increase startup costs. + /// 1.Increasing the stack size will increase startup costs. /// 2.The stack size contributes to the total memory size a contract can use, - /// which includes the contracts code size - #[arg(long = "stack-size")] - pub stack_size: Option, + /// which includes the contracts code size. + #[arg(long = "stack-size", default_value_t = PolkaVMDefaultStackMemorySize)] + pub stack_size: u32, } impl Arguments { /// Validates the arguments. pub fn validate(&self) -> Vec { + let argument_matches = Self::command().get_matches(); let mut messages = Vec::new(); if self.version && std::env::args().count() > 2 { @@ -226,9 +228,13 @@ impl Arguments { )); } - if self.metadata_hash == Some(MetadataHash::IPFS.to_string()) { + if self.metadata_hash == MetadataHash::IPFS { messages.push(SolcStandardJsonOutputError::new_error( - "`IPFS` metadata hash type is not supported. Please use `keccak256` instead.", + format!( + "`{}` metadata hash type is not supported. Please use `{}` instead.", + MetadataHash::IPFS, + MetadataHash::Keccak256, + ), None, None, )); @@ -350,34 +356,34 @@ impl Arguments { } if self.disable_solc_optimizer { messages.push(SolcStandardJsonOutputError::new_error( - "Disabling the solc optimizer must specified in standard JSON input settings.", + "Disabling the solc optimizer must be specified in standard JSON input settings.", None, None, )); } - if self.optimization.is_some() { + if Self::is_argument("optimization", &argument_matches) { messages.push(SolcStandardJsonOutputError::new_error( - "LLVM optimizations must specified in standard JSON input settings.", + "LLVM optimizations must be specified in standard JSON input settings.", None, None, )); } - if self.metadata_hash.is_some() { + if Self::is_argument("metadata_hash", &argument_matches) { messages.push(SolcStandardJsonOutputError::new_error( - "Metadata hash mode must specified in standard JSON input settings.", + "Metadata hash mode must be specified in standard JSON input settings.", None, None, )); } - if self.heap_size.is_some() { + if Self::is_argument("heap_size", &argument_matches) { messages.push(SolcStandardJsonOutputError::new_error( "Heap size must be specified in standard JSON input polkavm memory settings.", None, None, )); } - if self.stack_size.is_some() { + if Self::is_argument("stack_size", &argument_matches) { messages.push(SolcStandardJsonOutputError::new_error( "Stack size must be specified in standard JSON input polkavm memory settings.", None, @@ -403,6 +409,19 @@ impl Arguments { messages } + /// Checks whether the value from the argument with the given `argument_id` + /// came from the command line. + /// + /// This can be used for determining if a user has explicitly provided an + /// argument, even if it is the same value as the default one. + /// + /// Panics if the `id` is not a valid argument id. + fn is_argument(argument_id: &str, argument_matches: &ArgMatches) -> bool { + argument_matches + .value_source(argument_id) + .is_some_and(|source| source == ValueSource::CommandLine) + } + /// Returns remappings from input paths. pub fn split_input_files_and_remappings( &self, diff --git a/crates/resolc/src/resolc/main.rs b/crates/resolc/src/resolc/main.rs index 114621c..4d5934b 100644 --- a/crates/resolc/src/resolc/main.rs +++ b/crates/resolc/src/resolc/main.rs @@ -1,13 +1,10 @@ //! Solidity to PolkaVM compiler binary. -use std::str::FromStr; use std::{io::Write, path::PathBuf}; use clap::error::ErrorKind; use resolc::Process; -use revive_common::{ - deserialize_from_str, EVMVersion, MetadataHash, EXIT_CODE_FAILURE, EXIT_CODE_SUCCESS, -}; +use revive_common::{deserialize_from_str, EVMVersion, EXIT_CODE_FAILURE, EXIT_CODE_SUCCESS}; use revive_llvm_context::{initialize_llvm, DebugConfig, OptimizerSettings, PolkaVMTarget}; use revive_solc_json_interface::{ ResolcWarning, SolcStandardJsonInputSettingsPolkaVMMemory, @@ -32,7 +29,7 @@ fn main() -> anyhow::Result<()> { let is_standard_json = arguments.standard_json.is_some(); let mut messages = arguments.validate(); - if messages.iter().all(|error| error.severity != "error") { + if messages.iter().all(|error| !error.is_error()) { if !is_standard_json { std::io::stderr() .write_all( @@ -170,27 +167,21 @@ fn main_inner( None => None, }; - let mut optimizer_settings = match arguments.optimization { - Some(mode) => OptimizerSettings::try_from_cli(mode)?, - None => OptimizerSettings::size(), - }; + let mut optimizer_settings = OptimizerSettings::try_from_cli(arguments.optimization)?; optimizer_settings.is_verify_each_enabled = arguments.llvm_verify_each; optimizer_settings.is_debug_logging_enabled = arguments.llvm_debug_logging; - let metadata_hash = match arguments.metadata_hash { - Some(ref hash_type) => MetadataHash::from_str(hash_type.as_str())?, - None => MetadataHash::Keccak256, - }; - - let memory_config = - SolcStandardJsonInputSettingsPolkaVMMemory::new(arguments.heap_size, arguments.stack_size); + let memory_config = SolcStandardJsonInputSettingsPolkaVMMemory::new( + Some(arguments.heap_size), + Some(arguments.stack_size), + ); let build = if arguments.yul { resolc::yul( &solc, input_files.as_slice(), arguments.libraries.as_slice(), - metadata_hash, + arguments.metadata_hash, messages, optimizer_settings, debug_config, @@ -200,7 +191,7 @@ fn main_inner( } else if let Some(standard_json) = arguments.standard_json { resolc::standard_json( &solc, - metadata_hash, + arguments.metadata_hash, messages, standard_json.map(PathBuf::from), arguments.base_path, @@ -215,7 +206,7 @@ fn main_inner( &solc, input_files.as_slice(), arguments.libraries.as_slice(), - metadata_hash, + arguments.metadata_hash, messages, evm_version, format, @@ -240,7 +231,7 @@ fn main_inner( &solc, input_files.as_slice(), arguments.libraries.as_slice(), - metadata_hash, + arguments.metadata_hash, messages, evm_version, !arguments.disable_solc_optimizer, diff --git a/crates/resolc/src/tests/cli/standard_json.rs b/crates/resolc/src/tests/cli/standard_json.rs index e564d5f..fa98f3f 100644 --- a/crates/resolc/src/tests/cli/standard_json.rs +++ b/crates/resolc/src/tests/cli/standard_json.rs @@ -1,6 +1,8 @@ //! The tests for running resolc with standard JSON option. -use revive_solc_json_interface::SolcStandardJsonOutput; +use revive_solc_json_interface::{ + PolkaVMDefaultHeapMemorySize, PolkaVMDefaultStackMemorySize, SolcStandardJsonOutput, +}; use crate::cli_utils::{ assert_command_success, assert_equal_exit_codes, execute_resolc_with_stdin_input, @@ -10,12 +12,31 @@ use crate::cli_utils::{ const JSON_OPTION: &str = "--standard-json"; +/// Asserts that the standard JSON output has at least one error with the given `error_message`. +fn assert_standard_json_errors_contain(output: &SolcStandardJsonOutput, error_message: &str) { + assert!( + output + .errors + .iter() + .any(|error| error.is_error() && error.message.contains(error_message)), + "the standard JSON output should contain the error message `{error_message}`" + ); +} + +/// Converts valid JSON text to `SolcStandardJsonOutput`. +fn to_solc_standard_json_output(json_text: &str) -> SolcStandardJsonOutput { + serde_json::from_str(json_text).unwrap() +} + #[test] fn runs_with_valid_input_file() { let arguments = &[JSON_OPTION]; let resolc_result = execute_resolc_with_stdin_input(arguments, STANDARD_JSON_CONTRACTS_PATH); assert_command_success(&resolc_result, "Providing a valid input file to stdin"); + let resolc_output = to_solc_standard_json_output(&resolc_result.stdout); + assert!(!resolc_output.errors.iter().any(|error| error.is_error())); + assert!( resolc_result.stdout.contains("contracts"), "Expected the output to contain a `contracts` field." @@ -30,7 +51,7 @@ fn no_evm_codegen_requested() { let result = execute_resolc_with_stdin_input(&[JSON_OPTION], STANDARD_JSON_NO_EVM_CODEGEN_PATH); assert_command_success(&result, "EVM codegen std json input fixture should build"); - let output: SolcStandardJsonOutput = serde_json::from_str(&result.stdout).unwrap(); + let output = to_solc_standard_json_output(&result.stdout); assert!(!output.errors.iter().any(|msg| msg.severity == "error")) } @@ -49,6 +70,91 @@ fn no_evm_codegen_requested_complex() { "the EVM codegen std json complext input fixture should build fine", ); - let output: SolcStandardJsonOutput = serde_json::from_str(&result.stdout).unwrap(); + let output = to_solc_standard_json_output(&result.stdout); assert!(!output.errors.iter().any(|msg| msg.severity == "error")) } + +#[test] +fn invalid_extra_arguments() { + struct TestCase<'a> { + arguments: Vec<&'a str>, + error_message: &'static str, + } + + let default_heap_size_string = PolkaVMDefaultHeapMemorySize.to_string(); + let default_stack_size_string = PolkaVMDefaultStackMemorySize.to_string(); + + let cases = vec![ + TestCase { + arguments: vec![JSON_OPTION, "--heap-size", &default_heap_size_string], + error_message: + "Heap size must be specified in standard JSON input polkavm memory settings", + }, + TestCase { + arguments: vec![JSON_OPTION, "--stack-size", &default_stack_size_string], + error_message: + "Stack size must be specified in standard JSON input polkavm memory settings", + }, + TestCase { + arguments: vec![JSON_OPTION, "--optimization", "z"], + error_message: "LLVM optimizations must be specified in standard JSON input settings", + }, + TestCase { + arguments: vec![JSON_OPTION, "-Oz"], + error_message: "LLVM optimizations must be specified in standard JSON input settings", + }, + TestCase { + arguments: vec![JSON_OPTION, "--metadata-hash", "ipfs"], + error_message: + "`ipfs` metadata hash type is not supported. Please use `keccak256` instead", + }, + TestCase { + arguments: vec![JSON_OPTION, "--metadata-hash", "keccak256"], + error_message: "Metadata hash mode must be specified in standard JSON input settings", + }, + TestCase { + arguments: vec![JSON_OPTION, "--asm"], + error_message: "Cannot output assembly or binary outside of JSON in standard JSON mode", + }, + TestCase { + arguments: vec![JSON_OPTION, "--bin"], + error_message: "Cannot output assembly or binary outside of JSON in standard JSON mode", + }, + TestCase { + arguments: vec![JSON_OPTION, "--libraries", "myLib=0x0000"], + error_message: "Libraries must be passed via standard JSON input", + }, + TestCase { + arguments: vec![JSON_OPTION, "--evm-version", "osaka"], + error_message: "EVM version must be passed via standard JSON input", + }, + TestCase { + arguments: vec![JSON_OPTION, "--output-dir", "tmp"], + error_message: "Output directory cannot be used in standard JSON mode", + }, + TestCase { + arguments: vec![JSON_OPTION, "--overwrite"], + error_message: "Overwriting flag cannot be used in standard JSON mode", + }, + TestCase { + arguments: vec![JSON_OPTION, "--disable-solc-optimizer"], + error_message: + "Disabling the solc optimizer must be specified in standard JSON input settings", + }, + TestCase { + arguments: vec![JSON_OPTION, "-g"], + error_message: "Debug info must be requested in standard JSON input polkavm settings", + }, + TestCase { + arguments: vec![JSON_OPTION, "--llvm-arg=-riscv"], + error_message: + "LLVM arguments must be configured in standard JSON input polkavm settings", + }, + ]; + + for case in cases { + let result = execute_resolc_with_stdin_input(&case.arguments, STANDARD_JSON_CONTRACTS_PATH); + let output = to_solc_standard_json_output(&result.stdout); + assert_standard_json_errors_contain(&output, case.error_message); + } +}