diff --git a/.gitattributes b/.gitattributes index 7cc205090b..d58eab2ee3 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,4 @@ *.* text eol=lf +*.png -text +*.jpg -text +*.gif -text diff --git a/.github/ISSUE_TEMPLATE/work_item.yml b/.github/ISSUE_TEMPLATE/work_item.yml index 5de8d547dd..79d5401ed2 100644 --- a/.github/ISSUE_TEMPLATE/work_item.yml +++ b/.github/ISSUE_TEMPLATE/work_item.yml @@ -1,23 +1,29 @@ name: Work item description: Submit an actionable task body: - - type: dropdown - id: tool-name + - type: textarea + id: current-state attributes: - label: Which components does the task require to be changed? (think hard pls) - multiple: true - options: - - snforge - - sncast - - cairo-profiler - - other (describe below) + label: Current State + description: Describe the current state and outline the problem + placeholder: Currently, the `print_a` cheatcode prints `"a"` to stdout. This is problematic because user might want it to be printed on their actual printer. validations: required: true - - type: textarea - id: description + - type: input + id: objective attributes: - label: Description - description: Describe the issue here. + label: Objective + description: Briefly describe the correct state + placeholder: The `print_a` cheatcode should magically detect if user wants to print to stdout or a printer. validations: required: true + + - type: textarea + id: additional-context + attributes: + label: Additional Context + description: Provide additional context on the desired state. + placeholder: If we can detect any printers in local network it might indicate that user wants to print to it. + validations: + required: false diff --git a/.github/workflows/scheduled.yml b/.github/workflows/scheduled.yml index 596c5d7449..68cab6cab8 100644 --- a/.github/workflows/scheduled.yml +++ b/.github/workflows/scheduled.yml @@ -11,6 +11,7 @@ on: jobs: get-scarb-versions: + if: "! github.event.repository.fork" name: Get Scarb versions outputs: versions: ${{ steps.get_versions.outputs.versions }} @@ -30,6 +31,7 @@ jobs: echo "versions=[${scarb_versions[@]}]" >> "$GITHUB_OUTPUT" test-forge-unit-and-integration: + if: "! github.event.repository.fork" runs-on: ubuntu-latest needs: get-scarb-versions strategy: @@ -50,6 +52,7 @@ jobs: - run: cargo test --release -p forge integration test-forge-e2e: + if: "! github.event.repository.fork" runs-on: ubuntu-latest needs: get-scarb-versions strategy: @@ -89,6 +92,7 @@ jobs: - run: cargo test --release -p forge e2e test-cast: + if: "! github.event.repository.fork" runs-on: ubuntu-latest needs: get-scarb-versions strategy: diff --git a/CHANGELOG.md b/CHANGELOG.md index 87c0f342ec..1d86a8e188 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.36.0] - 2025-01-15 + ### Forge #### Changed @@ -17,8 +19,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 #### Added +- When using `--max-fee` with transactions v3, calculated max gas and max gas unit price are automatically validated to ensure they are greater than 0 after conversion - interactive interface that allows setting created or imported account as the default +#### Changed + +- Values passed to the `--max-fee`, `--max-gas`, and `--max-gas-unit-price` flags must be greater than 0 + +#### Deprecated + +- `--version` flag + ## [0.35.1] - 2024-12-16 ### Forge diff --git a/Cargo.lock b/Cargo.lock index 041d984469..13aff201f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1896,7 +1896,7 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "docs" -version = "0.35.1" +version = "0.36.0" dependencies = [ "anyhow", "camino", @@ -2149,7 +2149,7 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "forge" -version = "0.35.1" +version = "0.36.0" dependencies = [ "anyhow", "assert_fs", @@ -2199,7 +2199,7 @@ dependencies = [ [[package]] name = "forge_runner" -version = "0.35.1" +version = "0.36.0" dependencies = [ "anyhow", "blockifier", @@ -2217,7 +2217,6 @@ dependencies = [ "cheatnet", "console", "conversions", - "fs4", "futures", "indoc", "itertools 0.12.1", @@ -2262,16 +2261,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "fs4" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29f9df8a11882c4e3335eb2d18a0137c505d9ca927470b0cac9c6f0ae07d28f7" -dependencies = [ - "rustix", - "windows-sys 0.48.0", -] - [[package]] name = "fs_extra" version = "1.3.0" @@ -4768,6 +4757,7 @@ dependencies = [ "anyhow", "cairo-lang-runner", "console", + "indicatif", "regex", "semver", "snapbox", @@ -4876,7 +4866,7 @@ dependencies = [ [[package]] name = "sncast" -version = "0.35.1" +version = "0.36.0" dependencies = [ "anyhow", "async-trait", @@ -4934,10 +4924,8 @@ dependencies = [ [[package]] name = "snforge_scarb_plugin" -version = "0.35.1" +version = "0.36.0" dependencies = [ - "cairo-lang-diagnostics", - "cairo-lang-filesystem", "cairo-lang-macro", "cairo-lang-parser", "cairo-lang-syntax", diff --git a/Cargo.toml b/Cargo.toml index 24851799cd..48ec65709c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ members = [ ] [workspace.package] -version = "0.35.1" +version = "0.36.0" edition = "2021" repository = "https://github.com/foundry-rs/starknet-foundry" license = "MIT" diff --git a/crates/conversions/src/lib.rs b/crates/conversions/src/lib.rs index 71af471e1d..2c625bcf3a 100644 --- a/crates/conversions/src/lib.rs +++ b/crates/conversions/src/lib.rs @@ -6,6 +6,9 @@ pub mod contract_address; pub mod entrypoint_selector; pub mod eth_address; pub mod felt; +pub mod non_zero_felt; +pub mod non_zero_u128; +pub mod non_zero_u64; pub mod nonce; pub mod padded_felt; pub mod primitive; diff --git a/crates/conversions/src/non_zero_felt.rs b/crates/conversions/src/non_zero_felt.rs new file mode 100644 index 0000000000..268c48b209 --- /dev/null +++ b/crates/conversions/src/non_zero_felt.rs @@ -0,0 +1,23 @@ +use crate::FromConv; +use starknet_types_core::felt::{Felt, NonZeroFelt}; +use std::num::{NonZeroU128, NonZeroU64}; + +impl FromConv for NonZeroFelt { + fn from_(value: NonZeroU64) -> Self { + NonZeroFelt::try_from(Felt::from(value.get())).unwrap_or_else(|_| { + unreachable!( + "NonZeroU64 is always greater than 0, so it should be convertible to NonZeroFelt" + ) + }) + } +} + +impl FromConv for NonZeroFelt { + fn from_(value: NonZeroU128) -> Self { + NonZeroFelt::try_from(Felt::from(value.get())).unwrap_or_else(|_| { + unreachable!( + "NonZeroU128 is always greater than 0, so it should be convertible to NonZeroFelt" + ) + }) + } +} diff --git a/crates/conversions/src/non_zero_u128.rs b/crates/conversions/src/non_zero_u128.rs new file mode 100644 index 0000000000..2e65c1d7ab --- /dev/null +++ b/crates/conversions/src/non_zero_u128.rs @@ -0,0 +1,14 @@ +use crate::TryFromConv; +use starknet_types_core::felt::{Felt, NonZeroFelt}; +use std::num::{NonZero, NonZeroU128}; + +impl TryFromConv for NonZeroU128 { + type Error = String; + fn try_from_(value: NonZeroFelt) -> Result { + let value: u128 = Felt::from(value) + .try_into() + .map_err(|_| "felt was too large to fit in u128")?; + Ok(NonZero::new(value) + .unwrap_or_else(|| unreachable!("non zero felt is always greater than 0"))) + } +} diff --git a/crates/conversions/src/non_zero_u64.rs b/crates/conversions/src/non_zero_u64.rs new file mode 100644 index 0000000000..6459cb089d --- /dev/null +++ b/crates/conversions/src/non_zero_u64.rs @@ -0,0 +1,14 @@ +use crate::TryFromConv; +use starknet_types_core::felt::{Felt, NonZeroFelt}; +use std::num::{NonZero, NonZeroU64}; + +impl TryFromConv for NonZeroU64 { + type Error = String; + fn try_from_(value: NonZeroFelt) -> Result { + let value: u64 = Felt::from(value) + .try_into() + .map_err(|_| "felt was too large to fit in u64")?; + Ok(NonZero::new(value) + .unwrap_or_else(|| unreachable!("non zero felt is always greater than 0"))) + } +} diff --git a/crates/conversions/src/serde/deserialize/deserialize_impl.rs b/crates/conversions/src/serde/deserialize/deserialize_impl.rs index fc31ddb067..668521a305 100644 --- a/crates/conversions/src/serde/deserialize/deserialize_impl.rs +++ b/crates/conversions/src/serde/deserialize/deserialize_impl.rs @@ -3,8 +3,8 @@ use crate::{byte_array::ByteArray, IntoConv}; use num_traits::cast::ToPrimitive; use starknet::providers::Url; use starknet_api::core::{ClassHash, ContractAddress, EntryPointSelector, Nonce}; -use starknet_types_core::felt::Felt; -use std::num::NonZeroU32; +use starknet_types_core::felt::{Felt, NonZeroFelt}; +use std::num::NonZero; impl CairoDeserialize for Url { fn deserialize(reader: &mut BufferReader<'_>) -> BufferReadResult { @@ -13,12 +13,6 @@ impl CairoDeserialize for Url { } } -impl CairoDeserialize for NonZeroU32 { - fn deserialize(reader: &mut BufferReader<'_>) -> BufferReadResult { - NonZeroU32::new(reader.read()?).ok_or(BufferReadError::ParseFailed) - } -} - impl CairoDeserialize for Felt { fn deserialize(reader: &mut BufferReader<'_>) -> BufferReadResult { reader.read_felt() @@ -71,6 +65,24 @@ impl CairoDeserialize for bool { } } +impl CairoDeserialize for NonZeroFelt { + fn deserialize(reader: &mut BufferReader<'_>) -> BufferReadResult { + let felt = reader.read::()?; + NonZeroFelt::try_from(felt).map_err(|_| BufferReadError::ParseFailed) + } +} + +macro_rules! impl_deserialize_for_nonzero_num_type { + ($type:ty) => { + impl CairoDeserialize for NonZero<$type> { + fn deserialize(reader: &mut BufferReader<'_>) -> BufferReadResult { + let val = <$type>::deserialize(reader)?; + NonZero::new(val).ok_or(BufferReadError::ParseFailed) + } + } + }; +} + macro_rules! impl_deserialize_for_felt_type { ($type:ty) => { impl CairoDeserialize for $type { @@ -80,15 +92,13 @@ macro_rules! impl_deserialize_for_felt_type { } }; } + macro_rules! impl_deserialize_for_num_type { ($type:ty) => { impl CairoDeserialize for $type { fn deserialize(reader: &mut BufferReader<'_>) -> BufferReadResult { let felt = Felt::deserialize(reader)?; - - felt.to_bigint() - .try_into() - .map_err(|_| BufferReadError::ParseFailed) + felt.try_into().map_err(|_| BufferReadError::ParseFailed) } } }; @@ -99,6 +109,10 @@ impl_deserialize_for_felt_type!(ContractAddress); impl_deserialize_for_felt_type!(Nonce); impl_deserialize_for_felt_type!(EntryPointSelector); +impl_deserialize_for_nonzero_num_type!(u32); +impl_deserialize_for_nonzero_num_type!(u64); +impl_deserialize_for_nonzero_num_type!(u128); + impl_deserialize_for_num_type!(u8); impl_deserialize_for_num_type!(u16); impl_deserialize_for_num_type!(u32); diff --git a/crates/conversions/tests/e2e/mod.rs b/crates/conversions/tests/e2e/mod.rs index a030ba2019..98a2224299 100644 --- a/crates/conversions/tests/e2e/mod.rs +++ b/crates/conversions/tests/e2e/mod.rs @@ -3,6 +3,9 @@ mod contract_address; mod entrypoint_selector; mod felt; mod field_elements; +mod non_zero_felt; +mod non_zero_u128; +mod non_zero_u64; mod nonce; mod padded_felt; mod string; diff --git a/crates/conversions/tests/e2e/non_zero_felt.rs b/crates/conversions/tests/e2e/non_zero_felt.rs new file mode 100644 index 0000000000..758bc86fb7 --- /dev/null +++ b/crates/conversions/tests/e2e/non_zero_felt.rs @@ -0,0 +1,21 @@ +#[cfg(test)] +mod tests_non_zero_felt { + use std::num::{NonZeroU128, NonZeroU64}; + + use conversions::FromConv; + use starknet_types_core::felt::{Felt, NonZeroFelt}; + + #[test] + fn test_happy_case() { + let non_zero_felt = NonZeroFelt::try_from(Felt::from(1_u8)).unwrap(); + + assert_eq!( + non_zero_felt, + NonZeroFelt::from_(NonZeroU64::new(1).unwrap()) + ); + assert_eq!( + non_zero_felt, + NonZeroFelt::from_(NonZeroU128::new(1).unwrap()) + ); + } +} diff --git a/crates/conversions/tests/e2e/non_zero_u128.rs b/crates/conversions/tests/e2e/non_zero_u128.rs new file mode 100644 index 0000000000..b79b3f6b67 --- /dev/null +++ b/crates/conversions/tests/e2e/non_zero_u128.rs @@ -0,0 +1,36 @@ +#[cfg(test)] +mod tests_non_zero_u128 { + use conversions::TryFromConv; + use starknet_types_core::felt::{Felt, NonZeroFelt}; + use std::num::NonZeroU128; + + #[test] + fn test_happy_case() { + let non_zero_u128 = NonZeroU128::new(1).unwrap(); + + assert_eq!( + non_zero_u128, + NonZeroU128::try_from_(NonZeroFelt::try_from(Felt::from(1_u8)).unwrap()).unwrap() + ); + } + + #[test] + fn test_limit() { + let felt = Felt::from_dec_str(&u128::MAX.to_string()).unwrap(); + let non_zero_felt = NonZeroFelt::try_from(felt).unwrap(); + + let result = NonZeroU128::try_from_(non_zero_felt); + assert!(result.is_ok()); + assert_eq!(result.unwrap().get(), u128::MAX); + } + + #[test] + fn test_felt_too_large() { + let large_felt = Felt::TWO.pow(128_u8); + let non_zero_felt = NonZeroFelt::try_from(large_felt).unwrap(); + + let result = NonZeroU128::try_from_(non_zero_felt); + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), "felt was too large to fit in u128"); + } +} diff --git a/crates/conversions/tests/e2e/non_zero_u64.rs b/crates/conversions/tests/e2e/non_zero_u64.rs new file mode 100644 index 0000000000..5a9a141dbd --- /dev/null +++ b/crates/conversions/tests/e2e/non_zero_u64.rs @@ -0,0 +1,36 @@ +#[cfg(test)] +mod tests_non_zero_u64 { + use conversions::TryFromConv; + use starknet_types_core::felt::{Felt, NonZeroFelt}; + use std::num::NonZeroU64; + + #[test] + fn test_happy_case() { + let non_zero_u64 = NonZeroU64::new(1).unwrap(); + + assert_eq!( + non_zero_u64, + NonZeroU64::try_from_(NonZeroFelt::try_from(Felt::from(1_u8)).unwrap()).unwrap() + ); + } + + #[test] + fn test_limit() { + let felt = Felt::from_dec_str(&u64::MAX.to_string()).unwrap(); + let non_zero_felt = NonZeroFelt::try_from(felt).unwrap(); + + let result = NonZeroU64::try_from_(non_zero_felt); + assert!(result.is_ok()); + assert_eq!(result.unwrap().get(), u64::MAX); + } + + #[test] + fn test_felt_too_large() { + let large_felt = Felt::TWO.pow(64_u8); + let non_zero_felt = NonZeroFelt::try_from(large_felt).unwrap(); + + let result = NonZeroU64::try_from_(non_zero_felt); + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), "felt was too large to fit in u64"); + } +} diff --git a/crates/forge-runner/Cargo.toml b/crates/forge-runner/Cargo.toml index ae0dfec1c1..1af06140d5 100644 --- a/crates/forge-runner/Cargo.toml +++ b/crates/forge-runner/Cargo.toml @@ -43,6 +43,5 @@ conversions = { path = "../conversions" } scarb-api = { path = "../scarb-api" } shared = { path = "../shared" } universal-sierra-compiler-api = { path = "../universal-sierra-compiler-api" } -fs4.workspace = true which.workspace = true sanitize-filename.workspace = true diff --git a/crates/forge-runner/src/lib.rs b/crates/forge-runner/src/lib.rs index 93fee082b5..59eec2bf67 100644 --- a/crates/forge-runner/src/lib.rs +++ b/crates/forge-runner/src/lib.rs @@ -13,6 +13,7 @@ use futures::StreamExt; use package_tests::with_config_resolved::TestCaseWithResolvedConfig; use profiler_api::run_profiler; use shared::print::print_as_warning; +use shared::spinner::Spinner; use std::collections::HashMap; use std::path::PathBuf; use std::sync::Arc; @@ -68,6 +69,7 @@ pub fn maybe_save_trace_and_profile( let name = sanitize_filename::sanitize(name.replace("::", "_")); let trace_path = save_trace_data(&name, trace_data)?; if execution_data_to_save.profile { + let _spinner = Spinner::create_with_message("Running cairo-profiler"); run_profiler(&name, &trace_path, &execution_data_to_save.additional_args)?; } return Ok(Some(trace_path)); @@ -84,6 +86,7 @@ pub fn maybe_generate_coverage( if saved_trace_data_paths.is_empty() { print_as_warning(&anyhow!("No trace data to generate coverage from")); } else { + let _spinner = Spinner::create_with_message("Running cairo-coverage"); run_coverage( saved_trace_data_paths, &execution_data_to_save.additional_args, diff --git a/crates/forge/src/lib.rs b/crates/forge/src/lib.rs index 93e4cbc8ad..3d8fc7a0dc 100644 --- a/crates/forge/src/lib.rs +++ b/crates/forge/src/lib.rs @@ -56,7 +56,7 @@ Read the docs: Join the community: - Follow core developers on X: https://twitter.com/swmansionxyz - Get support via Telegram: https://t.me/starknet_foundry_support -- Or discord: https://discord.gg/KZWaFtPZJf +- Or discord: https://discord.gg/starknet-community - Or join our general chat (Telegram): https://t.me/starknet_foundry Report bugs: https://github.com/foundry-rs/starknet-foundry/issues/new/choose\ diff --git a/crates/shared/Cargo.toml b/crates/shared/Cargo.toml index 02370a8e3f..2ed5e1a797 100644 --- a/crates/shared/Cargo.toml +++ b/crates/shared/Cargo.toml @@ -13,3 +13,4 @@ starknet.workspace = true url.workspace = true regex.workspace = true snapbox.workspace = true +indicatif.workspace = true diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index 348e3da3a0..a806e62024 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -10,6 +10,7 @@ pub mod command; pub mod consts; pub mod print; pub mod rpc; +pub mod spinner; pub mod test_utils; pub mod utils; diff --git a/crates/shared/src/spinner.rs b/crates/shared/src/spinner.rs new file mode 100644 index 0000000000..a6b9d7326f --- /dev/null +++ b/crates/shared/src/spinner.rs @@ -0,0 +1,25 @@ +use indicatif::{ProgressBar, ProgressStyle}; +use std::borrow::Cow; +use std::time::Duration; + +/// Styled spinner that uses [`ProgressBar`]. +/// Automatically finishes and clears itself when dropped. +pub struct Spinner(ProgressBar); +impl Spinner { + /// Create [`Spinner`] with a message. + pub fn create_with_message(message: impl Into>) -> Self { + let spinner = ProgressBar::new_spinner(); + let style = ProgressStyle::with_template("\n{spinner} {msg}\n") + .expect("template is static str and should be valid"); + spinner.set_style(style); + spinner.enable_steady_tick(Duration::from_millis(100)); + spinner.set_message(message); + Self(spinner) + } +} + +impl Drop for Spinner { + fn drop(&mut self) { + self.0.finish_and_clear(); + } +} diff --git a/crates/sncast/src/helpers/fee.rs b/crates/sncast/src/helpers/fee.rs index 7492961687..9bb983d1eb 100644 --- a/crates/sncast/src/helpers/fee.rs +++ b/crates/sncast/src/helpers/fee.rs @@ -1,12 +1,14 @@ -use anyhow::{bail, ensure, Error, Result}; +use anyhow::{bail, ensure, Context, Error, Result}; use clap::{Args, ValueEnum}; -use conversions::serde::deserialize::CairoDeserialize; -use conversions::TryIntoConv; +use conversions::{serde::deserialize::CairoDeserialize, FromConv, TryFromConv}; use shared::print::print_as_warning; use starknet::core::types::BlockId; use starknet::providers::Provider; use starknet_types_core::felt::{Felt, NonZeroFelt}; -use std::str::FromStr; +use std::{ + num::{NonZeroU128, NonZeroU64}, + str::FromStr, +}; #[derive(Args, Debug, Clone)] pub struct FeeArgs { @@ -15,16 +17,16 @@ pub struct FeeArgs { pub fee_token: Option, /// Max fee for the transaction. If not provided, will be automatically estimated. - #[clap(short, long)] - pub max_fee: Option, + #[clap(value_parser = parse_non_zero_felt, short, long)] + pub max_fee: Option, /// Max gas amount. If not provided, will be automatically estimated. (Only for STRK fee payment) - #[clap(long)] - pub max_gas: Option, + #[clap(value_parser = parse_non_zero_felt, long)] + pub max_gas: Option, /// Max gas price in Fri. If not provided, will be automatically estimated. (Only for STRK fee payment) - #[clap(long)] - pub max_gas_unit_price: Option, + #[clap(value_parser = parse_non_zero_felt, long)] + pub max_gas_unit_price: Option, } impl From for FeeArgs { @@ -43,8 +45,8 @@ impl From for FeeArgs { } => Self { fee_token: Some(FeeToken::Strk), max_fee, - max_gas: max_gas.map(Into::into), - max_gas_unit_price: max_gas_unit_price.map(Into::into), + max_gas: max_gas.map(NonZeroFelt::from_), + max_gas_unit_price: max_gas_unit_price.map(NonZeroFelt::from_), }, } } @@ -59,6 +61,7 @@ impl FeeArgs { } } + #[allow(clippy::too_many_lines)] pub async fn try_into_fee_settings( &self, provider: P, @@ -83,53 +86,76 @@ impl FeeArgs { (Some(_), Some(_), Some(_)) => { bail!("Passing all --max-fee, --max-gas and --max-gas-unit-price is conflicting. Please pass only two of them or less") } - (Some(max_fee), Some(max_gas), None) if max_fee < max_gas => { - bail!("--max-fee should be greater than or equal to --max-gas amount") - } - (Some(max_fee), None, Some(max_gas_unit_price)) - if max_fee < max_gas_unit_price => - { - bail!("--max-fee should be greater than or equal to --max-gas-unit-price") - } (None, _, _) => FeeSettings::Strk { - max_gas: self.max_gas.map(TryIntoConv::try_into_).transpose()?, + max_gas: self + .max_gas + .map(NonZeroU64::try_from_) + .transpose() + .map_err(anyhow::Error::msg)?, max_gas_unit_price: self .max_gas_unit_price - .map(TryIntoConv::try_into_) - .transpose()?, - }, - (Some(max_fee), None, Some(max_gas_unit_price)) => FeeSettings::Strk { - max_gas: Some( - max_fee - .floor_div(&NonZeroFelt::from_felt_unchecked(max_gas_unit_price)) - .try_into_()?, - ), - max_gas_unit_price: Some(max_gas_unit_price.try_into_()?), - }, - (Some(max_fee), Some(max_gas), None) => FeeSettings::Strk { - max_gas: Some(max_gas.try_into_()?), - max_gas_unit_price: Some( - max_fee - .floor_div(&NonZeroFelt::from_felt_unchecked(max_gas)) - .try_into_()?, - ), + .map(NonZeroU128::try_from_) + .transpose() + .map_err(anyhow::Error::msg)?, }, - (Some(max_fee), None, None) => { - let max_gas_unit_price = provider - .get_block_with_tx_hashes(block_id) - .await? - .l1_gas_price() - .price_in_fri; + (Some(max_fee), None, Some(max_gas_unit_price)) => { + if max_fee < max_gas_unit_price { + bail!( + "--max-fee should be greater than or equal to --max-gas-unit-price" + ); + } + + let max_gas = NonZeroFelt::try_from(Felt::from(max_fee).floor_div(&max_gas_unit_price)) + .unwrap_or_else(|_| unreachable!("Calculated max gas must be >= 1 because max_fee >= max_gas_unit_price ensures a positive result")); + print_max_fee_conversion_info(max_fee, max_gas, max_gas_unit_price); + FeeSettings::Strk { + max_gas: Some( + NonZeroU64::try_from_(max_gas).map_err(anyhow::Error::msg)?, + ), + max_gas_unit_price: Some( + NonZeroU128::try_from_(max_gas_unit_price) + .map_err(anyhow::Error::msg)?, + ), + } + } + (Some(max_fee), Some(max_gas), None) => { + if max_fee < max_gas { + bail!("--max-fee should be greater than or equal to --max-gas amount"); + } + let max_gas_unit_price = NonZeroFelt::try_from(Felt::from(max_fee).floor_div(&max_gas)) + .unwrap_or_else(|_| unreachable!("Calculated max gas unit price must be >= 1 because max_fee >= max_gas ensures a positive result")); + print_max_fee_conversion_info(max_fee, max_gas, max_gas_unit_price); FeeSettings::Strk { max_gas: Some( - max_fee - .floor_div(&NonZeroFelt::from_felt_unchecked( - max_gas_unit_price, - )) - .try_into_()?, + NonZeroU64::try_from_(max_gas).map_err(anyhow::Error::msg)?, + ), + max_gas_unit_price: Some( + NonZeroU128::try_from_(max_gas_unit_price) + .map_err(anyhow::Error::msg)?, + ), + } + } + (Some(max_fee), None, None) => { + let max_gas_unit_price = NonZeroFelt::try_from( + provider + .get_block_with_tx_hashes(block_id) + .await? + .l1_gas_price() + .price_in_fri, + )?; + // TODO(#2852) + let max_gas = NonZeroFelt::try_from(Felt::from(max_fee) + .floor_div(&max_gas_unit_price)).context("Calculated max-gas from provided --max-fee and the current network gas price is 0. Please increase --max-fee to obtain a positive gas amount")?; + print_max_fee_conversion_info(max_fee, max_gas, max_gas_unit_price); + FeeSettings::Strk { + max_gas: Some( + NonZeroU64::try_from_(max_gas).map_err(anyhow::Error::msg)?, + ), + max_gas_unit_price: Some( + NonZeroU128::try_from_(max_gas_unit_price) + .map_err(anyhow::Error::msg)?, ), - max_gas_unit_price: Some(max_gas_unit_price.try_into_()?), } } }; @@ -152,23 +178,23 @@ pub enum FeeToken { #[derive(Debug, PartialEq, CairoDeserialize)] pub enum ScriptFeeSettings { Eth { - max_fee: Option, + max_fee: Option, }, Strk { - max_fee: Option, - max_gas: Option, - max_gas_unit_price: Option, + max_fee: Option, + max_gas: Option, + max_gas_unit_price: Option, }, } #[derive(Debug, PartialEq)] pub enum FeeSettings { Eth { - max_fee: Option, + max_fee: Option, }, Strk { - max_gas: Option, - max_gas_unit_price: Option, + max_gas: Option, + max_gas_unit_price: Option, }, } @@ -248,7 +274,8 @@ impl FromStr for FeeToken { } fn parse_fee_token(s: &str) -> Result { - let deprecation_message = "Specifying '--fee-token' flag is deprecated and will be removed in the future. Use '--version' instead"; + let deprecation_message = + "Specifying '--fee-token' flag is deprecated and will be removed in the future."; print_as_warning(&Error::msg(deprecation_message)); let parsed_token: FeeToken = s.parse()?; @@ -261,3 +288,22 @@ fn parse_fee_token(s: &str) -> Result { Ok(parsed_token) } + +fn print_max_fee_conversion_info( + max_fee: impl Into, + max_gas: impl Into, + max_gas_unit_price: impl Into, +) { + let max_fee: Felt = max_fee.into(); + let max_gas: Felt = max_gas.into(); + let max_gas_unit_price: Felt = max_gas_unit_price.into(); + println!( + "Specifying '--max-fee' flag while using v3 transactions results in conversion to '--max-gas' and '--max-gas-unit-price' flags\nConverted {max_fee} max fee to {max_gas} max gas and {max_gas_unit_price} max gas unit price\n", + ); +} + +fn parse_non_zero_felt(s: &str) -> Result { + let felt: Felt = s.parse().map_err(|_| "Failed to parse value")?; + felt.try_into() + .map_err(|_| "Value should be greater than 0".to_string()) +} diff --git a/crates/sncast/src/helpers/mod.rs b/crates/sncast/src/helpers/mod.rs index 4dd3519e36..369c5019f5 100644 --- a/crates/sncast/src/helpers/mod.rs +++ b/crates/sncast/src/helpers/mod.rs @@ -8,3 +8,4 @@ pub mod fee; pub mod interactive; pub mod rpc; pub mod scarb_utils; +pub mod version; diff --git a/crates/sncast/src/helpers/version.rs b/crates/sncast/src/helpers/version.rs new file mode 100644 index 0000000000..954770db49 --- /dev/null +++ b/crates/sncast/src/helpers/version.rs @@ -0,0 +1,10 @@ +use anyhow::Error; +use clap::ValueEnum; +use shared::print::print_as_warning; + +const DEPRECATION_MESSAGE: &str = "The '--version' flag is deprecated and will be removed in the future. Version 3 will become the only type of transaction available."; + +pub fn parse_version(s: &str) -> Result { + print_as_warning(&Error::msg(DEPRECATION_MESSAGE)); + T::from_str(s, true) +} diff --git a/crates/sncast/src/main.rs b/crates/sncast/src/main.rs index 2db1f8e788..bb46ca5af7 100644 --- a/crates/sncast/src/main.rs +++ b/crates/sncast/src/main.rs @@ -63,7 +63,7 @@ Read the docs: Join the community: - Follow core developers on X: https://twitter.com/swmansionxyz - Get support via Telegram: https://t.me/starknet_foundry_support -- Or discord: https://discord.gg/KZWaFtPZJf +- Or discord: https://discord.gg/starknet-community - Or join our general chat (Telegram): https://t.me/starknet_foundry Report bugs: https://github.com/foundry-rs/starknet-foundry/issues/new/choose\ diff --git a/crates/sncast/src/starknet_commands/account/deploy.rs b/crates/sncast/src/starknet_commands/account/deploy.rs index 335c19194e..682bcf22b3 100644 --- a/crates/sncast/src/starknet_commands/account/deploy.rs +++ b/crates/sncast/src/starknet_commands/account/deploy.rs @@ -8,6 +8,7 @@ use sncast::helpers::constants::{BRAAVOS_BASE_ACCOUNT_CLASS_HASH, KEYSTORE_PASSW use sncast::helpers::error::token_not_supported_for_deployment; use sncast::helpers::fee::{FeeArgs, FeeSettings, FeeToken, PayableTransaction}; use sncast::helpers::rpc::RpcArgs; +use sncast::helpers::version::parse_version; use sncast::response::structs::InvokeResponse; use sncast::{ apply_optional, chain_id_to_network_name, check_account_file_exists, @@ -38,7 +39,7 @@ pub struct Deploy { pub fee_args: FeeArgs, /// Version of the account deployment (can be inferred from fee token) - #[clap(short, long)] + #[clap(short, long, value_parser = parse_version::)] pub version: Option, #[clap(flatten)] @@ -263,7 +264,11 @@ where let result = match fee_settings { FeeSettings::Eth { max_fee } => { let deployment = account_factory.deploy_v1(salt); - let deployment = apply_optional(deployment, max_fee, AccountDeploymentV1::max_fee); + let deployment = apply_optional( + deployment, + max_fee.map(Felt::from), + AccountDeploymentV1::max_fee, + ); deployment.send().await } FeeSettings::Strk { @@ -271,10 +276,14 @@ where max_gas_unit_price, } => { let deployment = account_factory.deploy_v3(salt); - let deployment = apply_optional(deployment, max_gas, AccountDeploymentV3::gas); let deployment = apply_optional( deployment, - max_gas_unit_price, + max_gas.map(std::num::NonZero::get), + AccountDeploymentV3::gas, + ); + let deployment = apply_optional( + deployment, + max_gas_unit_price.map(std::num::NonZero::get), AccountDeploymentV3::gas_price, ); deployment.send().await diff --git a/crates/sncast/src/starknet_commands/declare.rs b/crates/sncast/src/starknet_commands/declare.rs index d25a779d37..3efbd061a8 100644 --- a/crates/sncast/src/starknet_commands/declare.rs +++ b/crates/sncast/src/starknet_commands/declare.rs @@ -6,6 +6,7 @@ use scarb_api::StarknetContractArtifacts; use sncast::helpers::error::token_not_supported_for_declaration; use sncast::helpers::fee::{FeeArgs, FeeSettings, FeeToken, PayableTransaction}; use sncast::helpers::rpc::RpcArgs; +use sncast::helpers::version::parse_version; use sncast::response::errors::StarknetCommandError; use sncast::response::structs::{ AlreadyDeclaredResponse, DeclareResponse, DeclareTransactionResponse, @@ -44,7 +45,7 @@ pub struct Declare { pub package: Option, /// Version of the declaration (can be inferred from fee token) - #[clap(short, long)] + #[clap(short, long, value_parser = parse_version::)] pub version: Option, #[clap(flatten)] @@ -105,7 +106,8 @@ pub async fn declare( casm_class_hash, ); - let declaration = apply_optional(declaration, max_fee, DeclarationV2::max_fee); + let declaration = + apply_optional(declaration, max_fee.map(Felt::from), DeclarationV2::max_fee); let declaration = apply_optional(declaration, declare.nonce, DeclarationV2::nonce); declaration.send().await @@ -119,9 +121,16 @@ pub async fn declare( casm_class_hash, ); - let declaration = apply_optional(declaration, max_gas, DeclarationV3::gas); - let declaration = - apply_optional(declaration, max_gas_unit_price, DeclarationV3::gas_price); + let declaration = apply_optional( + declaration, + max_gas.map(std::num::NonZero::get), + DeclarationV3::gas, + ); + let declaration = apply_optional( + declaration, + max_gas_unit_price.map(std::num::NonZero::get), + DeclarationV3::gas_price, + ); let declaration = apply_optional(declaration, declare.nonce, DeclarationV3::nonce); declaration.send().await diff --git a/crates/sncast/src/starknet_commands/deploy.rs b/crates/sncast/src/starknet_commands/deploy.rs index 8b3bcb4e29..7990d0dfef 100644 --- a/crates/sncast/src/starknet_commands/deploy.rs +++ b/crates/sncast/src/starknet_commands/deploy.rs @@ -4,6 +4,7 @@ use conversions::IntoConv; use sncast::helpers::error::token_not_supported_for_deployment; use sncast::helpers::fee::{FeeArgs, FeeSettings, FeeToken, PayableTransaction}; use sncast::helpers::rpc::RpcArgs; +use sncast::helpers::version::parse_version; use sncast::response::errors::StarknetCommandError; use sncast::response::structs::DeployResponse; use sncast::{extract_or_generate_salt, impl_payable_transaction, udc_uniqueness}; @@ -43,7 +44,7 @@ pub struct Deploy { pub nonce: Option, /// Version of the deployment (can be inferred from fee token) - #[clap(short, long)] + #[clap(short, long, value_parser = parse_version::)] pub version: Option, #[clap(flatten)] @@ -91,7 +92,7 @@ pub async fn deploy( let execution = factory.deploy_v1(calldata.clone(), salt, unique); let execution = match max_fee { None => execution, - Some(max_fee) => execution.max_fee(max_fee), + Some(max_fee) => execution.max_fee(max_fee.into()), }; let execution = match nonce { None => execution, @@ -107,11 +108,11 @@ pub async fn deploy( let execution = match max_gas { None => execution, - Some(max_gas) => execution.gas(max_gas), + Some(max_gas) => execution.gas(max_gas.into()), }; let execution = match max_gas_unit_price { None => execution, - Some(max_gas_unit_price) => execution.gas_price(max_gas_unit_price), + Some(max_gas_unit_price) => execution.gas_price(max_gas_unit_price.into()), }; let execution = match nonce { None => execution, diff --git a/crates/sncast/src/starknet_commands/invoke.rs b/crates/sncast/src/starknet_commands/invoke.rs index 8d481d6cf8..215967d0e5 100644 --- a/crates/sncast/src/starknet_commands/invoke.rs +++ b/crates/sncast/src/starknet_commands/invoke.rs @@ -5,6 +5,7 @@ use conversions::IntoConv; use sncast::helpers::error::token_not_supported_for_invoke; use sncast::helpers::fee::{FeeArgs, FeeSettings, FeeToken, PayableTransaction}; use sncast::helpers::rpc::RpcArgs; +use sncast::helpers::version::parse_version; use sncast::response::errors::StarknetCommandError; use sncast::response::structs::InvokeResponse; use sncast::{apply_optional, handle_wait_for_tx, impl_payable_transaction, WaitForTx}; @@ -38,7 +39,7 @@ pub struct Invoke { pub nonce: Option, /// Version of invoke (can be inferred from fee token) - #[clap(short, long)] + #[clap(short, long, value_parser = parse_version::)] pub version: Option, #[clap(flatten)] @@ -89,7 +90,11 @@ pub async fn execute_calls( FeeSettings::Eth { max_fee } => { let execution_calls = account.execute_v1(calls); - let execution = apply_optional(execution_calls, max_fee, ExecutionV1::max_fee); + let execution = apply_optional( + execution_calls, + max_fee.map(Felt::from), + ExecutionV1::max_fee, + ); let execution = apply_optional(execution, nonce, ExecutionV1::nonce); execution.send().await } @@ -99,8 +104,16 @@ pub async fn execute_calls( } => { let execution_calls = account.execute_v3(calls); - let execution = apply_optional(execution_calls, max_gas, ExecutionV3::gas); - let execution = apply_optional(execution, max_gas_unit_price, ExecutionV3::gas_price); + let execution = apply_optional( + execution_calls, + max_gas.map(std::num::NonZero::get), + ExecutionV3::gas, + ); + let execution = apply_optional( + execution, + max_gas_unit_price.map(std::num::NonZero::get), + ExecutionV3::gas_price, + ); let execution = apply_optional(execution, nonce, ExecutionV3::nonce); execution.send().await } diff --git a/crates/sncast/src/starknet_commands/multicall/run.rs b/crates/sncast/src/starknet_commands/multicall/run.rs index 647ee55bc3..ce313e986a 100644 --- a/crates/sncast/src/starknet_commands/multicall/run.rs +++ b/crates/sncast/src/starknet_commands/multicall/run.rs @@ -8,6 +8,7 @@ use sncast::helpers::constants::UDC_ADDRESS; use sncast::helpers::error::token_not_supported_for_invoke; use sncast::helpers::fee::{FeeArgs, FeeToken, PayableTransaction}; use sncast::helpers::rpc::RpcArgs; +use sncast::helpers::version::parse_version; use sncast::response::errors::handle_starknet_command_error; use sncast::response::structs::InvokeResponse; use sncast::{extract_or_generate_salt, impl_payable_transaction, udc_uniqueness, WaitForTx}; @@ -31,7 +32,7 @@ pub struct Run { pub fee_args: FeeArgs, /// Version of invoke (can be inferred from fee token) - #[clap(short, long)] + #[clap(short, long, value_parser = parse_version::)] pub version: Option, #[clap(flatten)] diff --git a/crates/sncast/tests/e2e/account/deploy.rs b/crates/sncast/tests/e2e/account/deploy.rs index 4ea89bb962..de59901dcc 100644 --- a/crates/sncast/tests/e2e/account/deploy.rs +++ b/crates/sncast/tests/e2e/account/deploy.rs @@ -399,7 +399,7 @@ async fn test_fee_token_deprecation_warning_eth() { let snapbox = runner(&args).current_dir(tempdir.path()); snapbox.assert().success().stdout_matches(indoc! {r" - [WARNING] Specifying '--fee-token' flag is deprecated and will be removed in the future. Use '--version' instead + [WARNING] Specifying '--fee-token' flag is deprecated and will be removed in the future. [WARNING] Eth transactions will stop being supported in the future due to 'SNIP-16' Transaction hash: [..] command: account deploy @@ -432,7 +432,39 @@ async fn test_fee_token_deprecation_warning_strk() { let snapbox = runner(&args).current_dir(tempdir.path()); snapbox.assert().success().stdout_matches(indoc! {r" - [WARNING] Specifying '--fee-token' flag is deprecated and will be removed in the future. Use '--version' instead + [WARNING] Specifying '--fee-token' flag is deprecated and will be removed in the future. + Transaction hash: [..] + command: account deploy + transaction_hash: [..] + + To see invocation details, visit: + transaction: [..] + "}); +} + +#[tokio::test] +async fn test_version_deprecation_warning() { + let tempdir = create_account(false, &OZ_CLASS_HASH.into_hex_string(), "oz").await; + let accounts_file = "accounts.json"; + + let args = vec![ + "--accounts-file", + accounts_file, + "--wait", + "account", + "deploy", + "--url", + URL, + "--name", + "my_account", + "--version", + "v3", + ]; + + let snapbox = runner(&args).current_dir(tempdir.path()); + + snapbox.assert().success().stdout_matches(indoc! {r" + [WARNING] The '--version' flag is deprecated and will be removed in the future. Version 3 will become the only type of transaction available. Transaction hash: [..] command: account deploy transaction_hash: [..] @@ -463,6 +495,9 @@ pub async fn test_valid_class_hash() { let snapbox = runner(&args).current_dir(tempdir.path()); snapbox.assert().success().stdout_matches(indoc! {r" + Specifying '--max-fee' flag while using v3 transactions results in conversion to '--max-gas' and '--max-gas-unit-price' flags + Converted [..] max fee to [..] max gas and [..] max gas unit price + command: account deploy transaction_hash: [..] @@ -584,6 +619,9 @@ pub async fn test_happy_case_keystore(account_type: &str) { let snapbox = runner(&args).current_dir(tempdir.path()); snapbox.assert().stdout_matches(indoc! {r" + Specifying '--max-fee' flag while using v3 transactions results in conversion to '--max-gas' and '--max-gas-unit-price' flags + Converted [..] max fee to [..] max gas and [..] max gas unit price + command: account deploy transaction_hash: 0x0[..] @@ -857,6 +895,9 @@ pub async fn test_deploy_keystore_other_args() { let snapbox = runner(&args).current_dir(tempdir.path()); snapbox.assert().stdout_matches(indoc! {r" + Specifying '--max-fee' flag while using v3 transactions results in conversion to '--max-gas' and '--max-gas-unit-price' flags + Converted [..] max fee to [..] max gas and [..] max gas unit price + command: account deploy transaction_hash: 0x0[..] diff --git a/crates/sncast/tests/e2e/declare.rs b/crates/sncast/tests/e2e/declare.rs index f423075c82..50ce7f917d 100644 --- a/crates/sncast/tests/e2e/declare.rs +++ b/crates/sncast/tests/e2e/declare.rs @@ -729,3 +729,40 @@ async fn test_no_scarb_profile() { "}, ); } + +#[tokio::test] +async fn test_version_deprecation_warning() { + let contract_path = duplicate_contract_directory_with_salt( + CONTRACTS_DIR.to_string() + "/map", + "put", + "human_readable", + ); + let tempdir = create_and_deploy_oz_account().await; + join_tempdirs(&contract_path, &tempdir); + + let args = vec![ + "--accounts-file", + "accounts.json", + "--account", + "my_account", + "declare", + "--url", + URL, + "--contract-name", + "Map", + "--max-fee", + "99999999999999999", + "--version", + "v3", + ]; + + let snapbox = runner(&args).current_dir(tempdir.path()); + let output = snapbox.assert().success(); + + assert_stdout_contains( + output, + indoc! {r" + [WARNING] The '--version' flag is deprecated and will be removed in the future. Version 3 will become the only type of transaction available. + " }, + ); +} diff --git a/crates/sncast/tests/e2e/deploy.rs b/crates/sncast/tests/e2e/deploy.rs index 3de61512d4..eeb8191f00 100644 --- a/crates/sncast/tests/e2e/deploy.rs +++ b/crates/sncast/tests/e2e/deploy.rs @@ -463,3 +463,46 @@ async fn test_happy_case_shell() { .arg(CONSTRUCTOR_WITH_PARAMS_CONTRACT_CLASS_HASH_SEPOLIA); snapbox.assert().success(); } + +#[tokio::test] +async fn test_version_deprecation_warning() { + let tempdir = create_and_deploy_account(OZ_CLASS_HASH, AccountType::OpenZeppelin).await; + + let args = vec![ + "--accounts-file", + "accounts.json", + "--account", + "my_account", + "deploy", + "--url", + URL, + "--class-hash", + MAP_CONTRACT_CLASS_HASH_SEPOLIA, + "--salt", + "0x2", + "--unique", + "--max-fee", + "99999999999999999", + "--version", + "v3", + ]; + + let snapbox = runner(&args).current_dir(tempdir.path()); + let output = snapbox.assert().success(); + + assert_stdout_contains( + output, + indoc! { + " + [WARNING] The '--version' flag is deprecated and will be removed in the future. Version 3 will become the only type of transaction available. + command: deploy + contract_address: 0x0[..] + transaction_hash: 0x0[..] + + To see deployment details, visit: + contract: [..] + transaction: [..] + " + }, + ); +} diff --git a/crates/sncast/tests/e2e/invoke.rs b/crates/sncast/tests/e2e/invoke.rs index 63c85afba3..c2e64bd55e 100644 --- a/crates/sncast/tests/e2e/invoke.rs +++ b/crates/sncast/tests/e2e/invoke.rs @@ -128,11 +128,19 @@ async fn test_happy_case_strk(class_hash: Felt, account_type: AccountType) { ]; let snapbox = runner(&args).current_dir(tempdir.path()); - let output = snapbox.assert().success().get_output().stdout.clone(); + let output = snapbox.assert().success(); + let stdout = output.get_output().stdout.clone(); - let hash = get_transaction_hash(&output); + let hash = get_transaction_hash(&stdout); let receipt = get_transaction_receipt(hash).await; + assert_stdout_contains( + output, + indoc! { + "Specifying '--max-fee' flag while using v3 transactions results in conversion to '--max-gas' and '--max-gas-unit-price' flags + Converted [..] max fee to [..] max gas and [..] max gas unit price" + }, + ); assert!(matches!(receipt, Invoke(_))); } @@ -388,6 +396,78 @@ fn test_too_low_max_fee() { ); } +#[test] +fn test_max_gas_equal_to_zero() { + let args = vec![ + "--accounts-file", + ACCOUNT_FILE_PATH, + "--account", + "user11", + "--wait", + "invoke", + "--url", + URL, + "--contract-address", + MAP_CONTRACT_ADDRESS_SEPOLIA, + "--function", + "put", + "--calldata", + "0x1", + "0x2", + "--max-gas", + "0", + "--fee-token", + "strk", + ]; + + let snapbox = runner(&args); + let output = snapbox.assert().code(2); + + assert_stderr_contains( + output, + indoc! {r" + error: invalid value '0' for '--max-gas ': Value should be greater than 0 + "}, + ); +} + +#[test] +fn test_calculated_max_gas_equal_to_zero_when_max_fee_passed() { + let args = vec![ + "--accounts-file", + ACCOUNT_FILE_PATH, + "--account", + "user11", + "--wait", + "invoke", + "--url", + URL, + "--contract-address", + MAP_CONTRACT_ADDRESS_SEPOLIA, + "--function", + "put", + "--calldata", + "0x1", + "0x2", + "--max-fee", + "999999", + "--fee-token", + "strk", + ]; + + let snapbox = runner(&args); + let output = snapbox.assert().success(); + + // TODO(#2852) + assert_stderr_contains( + output, + indoc! {r" + command: invoke + error: Calculated max-gas from provided --max-fee and the current network gas price is 0. Please increase --max-fee to obtain a positive gas amount: Tried to create NonZeroFelt from 0 + "}, + ); +} + #[tokio::test] async fn test_happy_case_cairo_expression_calldata() { let tempdir = create_and_deploy_oz_account().await; @@ -452,3 +532,36 @@ async fn test_happy_case_shell() { .arg(DATA_TRANSFORMER_CONTRACT_ADDRESS_SEPOLIA); snapbox.assert().success(); } + +#[test] +fn test_version_deprecation_warning() { + let args = vec![ + "--accounts-file", + ACCOUNT_FILE_PATH, + "--account", + "oz", + "invoke", + "--url", + URL, + "--contract-address", + MAP_CONTRACT_ADDRESS_SEPOLIA, + "--function", + "put", + "--calldata", + "0x1 0x2", + "--max-fee", + "99999999999999999", + "--version", + "v3", + ]; + + let snapbox = runner(&args); + let output = snapbox.assert().success(); + + assert_stdout_contains( + output, + indoc! {" + [WARNING] The '--version' flag is deprecated and will be removed in the future. Version 3 will become the only type of transaction available. + "}, + ); +} diff --git a/crates/sncast/tests/e2e/multicall/run.rs b/crates/sncast/tests/e2e/multicall/run.rs index 557bed9c2f..338faa7264 100644 --- a/crates/sncast/tests/e2e/multicall/run.rs +++ b/crates/sncast/tests/e2e/multicall/run.rs @@ -317,3 +317,39 @@ async fn test_numeric_overflow() { "}, ); } + +#[tokio::test] +async fn test_version_deprecation_warning() { + let path = project_root::get_project_root().expect("failed to get project root path"); + let path = Path::new(&path) + .join(MULTICALL_CONFIGS_DIR) + .join("deploy_invoke.toml"); + let path = path.to_str().expect("failed converting path to str"); + + let args = vec![ + "--accounts-file", + ACCOUNT_FILE_PATH, + "--account", + "oz", + "multicall", + "run", + "--url", + URL, + "--path", + path, + "--version", + "v3", + ]; + + let snapbox = runner(&args); + let output = snapbox.assert(); + + output.stdout_matches(indoc! {r" + [WARNING] The '--version' flag is deprecated and will be removed in the future. Version 3 will become the only type of transaction available. + command: multicall run + transaction_hash: 0x0[..] + + To see invocation details, visit: + transaction: [..] + "}); +} diff --git a/crates/sncast/tests/integration/fee.rs b/crates/sncast/tests/integration/fee.rs index 8e94db6825..c6c30d2ed9 100644 --- a/crates/sncast/tests/integration/fee.rs +++ b/crates/sncast/tests/integration/fee.rs @@ -1,3 +1,5 @@ +use std::num::{NonZeroU128, NonZeroU64}; + use crate::helpers::constants::URL; use sncast::helpers::constants::OZ_CLASS_HASH; use sncast::helpers::fee::{FeeArgs, FeeSettings, FeeToken}; @@ -5,6 +7,7 @@ use starknet::accounts::{AccountFactory, OpenZeppelinAccountFactory}; use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::{JsonRpcClient, Provider}; use starknet::signers::{LocalWallet, SigningKey}; +use starknet_types_core::felt::Felt; use url::Url; const MAX_FEE: u64 = 1_000_000_000_000; @@ -26,7 +29,7 @@ async fn test_happy_case_eth() { let args = FeeArgs { fee_token: Some(FeeToken::Eth), - max_fee: Some(100_u32.into()), + max_fee: Some(Felt::from(100_u32).try_into().unwrap()), max_gas: None, max_gas_unit_price: None, }; @@ -39,7 +42,7 @@ async fn test_happy_case_eth() { assert_eq!( settings, FeeSettings::Eth { - max_fee: Some(100_u32.into()) + max_fee: Some(Felt::from(100_u32).try_into().unwrap()) } ); } @@ -50,8 +53,8 @@ async fn test_max_gas_eth() { let args = FeeArgs { fee_token: Some(FeeToken::Eth), - max_fee: Some(100_u32.into()), - max_gas: Some(100_u32.into()), + max_fee: Some(Felt::from(100_u32).try_into().unwrap()), + max_gas: Some(Felt::from(100_u32).try_into().unwrap()), max_gas_unit_price: None, }; @@ -71,9 +74,9 @@ async fn test_max_gas_unit_price_eth() { let args = FeeArgs { fee_token: Some(FeeToken::Eth), - max_fee: Some(100_u32.into()), + max_fee: Some(Felt::from(100).try_into().unwrap()), max_gas: None, - max_gas_unit_price: Some(100_u32.into()), + max_gas_unit_price: Some(Felt::from(100_u32).try_into().unwrap()), }; let error = args @@ -92,9 +95,9 @@ async fn test_all_args() { let args = FeeArgs { fee_token: Some(FeeToken::Strk), - max_fee: Some(100_u32.into()), - max_gas: Some(100_u32.into()), - max_gas_unit_price: Some(100_u32.into()), + max_fee: Some(Felt::from(100_u32).try_into().unwrap()), + max_gas: Some(Felt::from(100_u32).try_into().unwrap()), + max_gas_unit_price: Some(Felt::from(100_u32).try_into().unwrap()), }; let error = args @@ -113,8 +116,8 @@ async fn test_max_fee_less_than_max_gas() { let args = FeeArgs { fee_token: Some(FeeToken::Strk), - max_fee: Some(50_u32.into()), - max_gas: Some(100_u32.into()), + max_fee: Some(Felt::from(50_u32).try_into().unwrap()), + max_gas: Some(Felt::from(100_u32).try_into().unwrap()), max_gas_unit_price: None, }; @@ -134,9 +137,9 @@ async fn test_max_fee_less_than_max_gas_unit_price() { let args = FeeArgs { fee_token: Some(FeeToken::Strk), - max_fee: Some(50_u32.into()), + max_fee: Some(Felt::from(50_u32).try_into().unwrap()), max_gas: None, - max_gas_unit_price: Some(100_u32.into()), + max_gas_unit_price: Some(Felt::from(100).try_into().unwrap()), }; let error = args @@ -154,7 +157,7 @@ async fn test_strk_fee_get_max_fee() { let args = FeeArgs { fee_token: Some(FeeToken::Strk), - max_fee: Some(MAX_FEE.into()), + max_fee: Some(Felt::from(MAX_FEE).try_into().unwrap()), max_gas: None, max_gas_unit_price: None, }; @@ -169,10 +172,9 @@ async fn test_strk_fee_get_max_fee() { max_gas, max_gas_unit_price, } => { - assert_eq!( - u128::from(max_gas.unwrap()) * max_gas_unit_price.unwrap(), - MAX_FEE.into() - ); + let max_gas: u64 = max_gas.unwrap().into(); + let max_gas_unit_price: u128 = max_gas_unit_price.unwrap().into(); + assert_eq!(u128::from(max_gas) * max_gas_unit_price, MAX_FEE.into()); } FeeSettings::Eth { .. } => unreachable!(), } @@ -184,8 +186,8 @@ async fn test_strk_fee_get_max_fee_with_max_gas() { let args = FeeArgs { fee_token: Some(FeeToken::Strk), - max_fee: Some(MAX_FEE.into()), - max_gas: Some(1_000_000_u32.into()), + max_fee: Some(Felt::from(MAX_FEE).try_into().unwrap()), + max_gas: Some(Felt::from(1_000_000_u32).try_into().unwrap()), max_gas_unit_price: None, }; @@ -197,8 +199,8 @@ async fn test_strk_fee_get_max_fee_with_max_gas() { assert_eq!( settings, FeeSettings::Strk { - max_gas: Some(1_000_000), - max_gas_unit_price: Some(u128::from(MAX_FEE / 1_000_000)), + max_gas: Some(NonZeroU64::new(1_000_000).unwrap()), + max_gas_unit_price: Some(NonZeroU128::new((MAX_FEE / 1_000_000).into()).unwrap()), } ); @@ -207,10 +209,9 @@ async fn test_strk_fee_get_max_fee_with_max_gas() { max_gas, max_gas_unit_price, } => { - assert_eq!( - u128::from(max_gas.unwrap()) * max_gas_unit_price.unwrap(), - MAX_FEE.into() - ); + let max_gas: u64 = max_gas.unwrap().into(); + let max_gas_unit_price: u128 = max_gas_unit_price.unwrap().into(); + assert_eq!(u128::from(max_gas) * max_gas_unit_price, MAX_FEE.into()); } FeeSettings::Eth { .. } => unreachable!(), } @@ -223,8 +224,8 @@ async fn test_strk_fee_get_max_gas_and_max_gas_unit_price() { let args = FeeArgs { fee_token: Some(FeeToken::Strk), max_fee: None, - max_gas: Some(1_000_000_u32.into()), - max_gas_unit_price: Some(1_000_u32.into()), + max_gas: Some(Felt::from(1_000_000_u32).try_into().unwrap()), + max_gas_unit_price: Some(Felt::from(1_000_u32).try_into().unwrap()), }; let settings = args @@ -235,8 +236,8 @@ async fn test_strk_fee_get_max_gas_and_max_gas_unit_price() { assert_eq!( settings, FeeSettings::Strk { - max_gas: Some(1_000_000), - max_gas_unit_price: Some(1_000), + max_gas: Some(NonZeroU64::new(1_000_000).unwrap()), + max_gas_unit_price: Some(NonZeroU128::new(1_000).unwrap()), } ); } @@ -247,9 +248,9 @@ async fn test_strk_fee_get_max_fee_with_max_gas_unit_price() { let args = FeeArgs { fee_token: Some(FeeToken::Strk), - max_fee: Some(MAX_FEE.into()), + max_fee: Some(Felt::from(MAX_FEE).try_into().unwrap()), max_gas: None, - max_gas_unit_price: Some(1_000_u32.into()), + max_gas_unit_price: Some(Felt::from(1_000_u32).try_into().unwrap()), }; let settings = args @@ -260,8 +261,8 @@ async fn test_strk_fee_get_max_fee_with_max_gas_unit_price() { assert_eq!( settings, FeeSettings::Strk { - max_gas: Some(MAX_FEE / 1_000), - max_gas_unit_price: Some(1_000), + max_gas: Some(NonZeroU64::new(MAX_FEE / 1_000).unwrap()), + max_gas_unit_price: Some(NonZeroU128::new(1_000).unwrap()), } ); @@ -270,10 +271,9 @@ async fn test_strk_fee_get_max_fee_with_max_gas_unit_price() { max_gas, max_gas_unit_price, } => { - assert_eq!( - u128::from(max_gas.unwrap()) * max_gas_unit_price.unwrap(), - MAX_FEE.into() - ); + let max_gas: u64 = max_gas.unwrap().into(); + let max_gas_unit_price: u128 = max_gas_unit_price.unwrap().into(); + assert_eq!(u128::from(max_gas) * max_gas_unit_price, MAX_FEE.into()); } FeeSettings::Eth { .. } => unreachable!(), } diff --git a/crates/snforge-scarb-plugin/Cargo.toml b/crates/snforge-scarb-plugin/Cargo.toml index 502b2c3118..0667da2e82 100644 --- a/crates/snforge-scarb-plugin/Cargo.toml +++ b/crates/snforge-scarb-plugin/Cargo.toml @@ -13,8 +13,6 @@ cairo-lang-macro.workspace = true cairo-lang-parser.workspace = true cairo-lang-utils.workspace = true cairo-lang-syntax.workspace = true -cairo-lang-diagnostics.workspace = true -cairo-lang-filesystem.workspace = true url.workspace = true indoc.workspace = true smol_str.workspace = true diff --git a/crates/snforge-scarb-plugin/Scarb.toml b/crates/snforge-scarb-plugin/Scarb.toml index 6c92561508..161e76a975 100644 --- a/crates/snforge-scarb-plugin/Scarb.toml +++ b/crates/snforge-scarb-plugin/Scarb.toml @@ -1,6 +1,6 @@ [package] name = "snforge_scarb_plugin" -version = "0.35.1" +version = "0.36.0" edition = "2024_07" [cairo-plugin] diff --git a/crates/universal-sierra-compiler-api/src/lib.rs b/crates/universal-sierra-compiler-api/src/lib.rs index 2efc16c690..95f6dac51d 100644 --- a/crates/universal-sierra-compiler-api/src/lib.rs +++ b/crates/universal-sierra-compiler-api/src/lib.rs @@ -9,7 +9,7 @@ use std::io::Write; use std::str::from_utf8; use tempfile::Builder; -use crate::spinner::spawn_spinner_message; +use crate::spinner::spawn_usc_spinner; pub use command::*; use shared::command::CommandExt; @@ -72,25 +72,25 @@ pub fn compile_sierra_at_path( sierra_file_path: &Utf8Path, sierra_type: &SierraType, ) -> Result { - let spinner = spawn_spinner_message(sierra_file_path)?; - - let mut usc_command = UniversalSierraCompilerCommand::new(); - let usc_output = usc_command - .inherit_stderr() - .args(vec![ - &("compile-".to_string() + &sierra_type.to_string()), - "--sierra-path", - sierra_file_path.as_str(), - ]) - .command() - .output_checked() - .context( - "Error while compiling Sierra. \ + let usc_output = { + let _spinner = spawn_usc_spinner(sierra_file_path)?; + + let mut usc_command = UniversalSierraCompilerCommand::new(); + usc_command + .inherit_stderr() + .args(vec![ + &("compile-".to_string() + &sierra_type.to_string()), + "--sierra-path", + sierra_file_path.as_str(), + ]) + .command() + .output_checked() + .context( + "Error while compiling Sierra. \ Make sure you have the latest universal-sierra-compiler binary installed. \ Contact us if it doesn't help", - )?; - - spinner.finish_and_clear(); + )? + }; Ok(T::convert(from_utf8(&usc_output.stdout)?.to_string())) } diff --git a/crates/universal-sierra-compiler-api/src/spinner.rs b/crates/universal-sierra-compiler-api/src/spinner.rs index f382b757a4..3137c13ab4 100644 --- a/crates/universal-sierra-compiler-api/src/spinner.rs +++ b/crates/universal-sierra-compiler-api/src/spinner.rs @@ -1,14 +1,9 @@ use anyhow::Result; use camino::Utf8Path; -use indicatif::{ProgressBar, ProgressStyle}; +use shared::spinner::Spinner; use std::env; -use std::time::Duration; - -pub fn spawn_spinner_message(sierra_file_path: &Utf8Path) -> Result { - let spinner = ProgressBar::new_spinner(); - spinner.set_style(ProgressStyle::with_template("\n{spinner} {msg}\n")?); - spinner.enable_steady_tick(Duration::from_millis(100)); +pub fn spawn_usc_spinner(sierra_file_path: &Utf8Path) -> Result { // Skip printing path when compiling unsaved sierra // which occurs during test execution for some cheatcodes e.g. `replace_bytecode` let message = if is_temp_file(sierra_file_path)? { @@ -19,7 +14,7 @@ pub fn spawn_spinner_message(sierra_file_path: &Utf8Path) -> Result sierra_file_path.canonicalize_utf8()? ) }; - spinner.set_message(message); + let spinner = Spinner::create_with_message(message); Ok(spinner) } diff --git a/docs/src/appendix/sncast/account/deploy.md b/docs/src/appendix/sncast/account/deploy.md index 0357908ee1..9bea12f7b4 100644 --- a/docs/src/appendix/sncast/account/deploy.md +++ b/docs/src/appendix/sncast/account/deploy.md @@ -16,7 +16,7 @@ Overrides url from `snfoundry.toml`. ## `--max-fee, -m ` Optional. -Maximum fee for the `deploy_account` transaction in Fri or Wei depending on fee token or transaction version. When not used, defaults to auto-estimation. +Maximum fee for the `deploy_account` transaction in Fri or Wei depending on fee token or transaction version. When not used, defaults to auto-estimation. Must be greater than zero. ## `--fee-token ` Optional. When not used, defaults to STRK. @@ -26,12 +26,12 @@ Token used for fee payment. Possible values: ETH, STRK. ## `--max-gas ` Optional. -Maximum gas for the `deploy_account` transaction. When not used, defaults to auto-estimation. (Only for STRK fee payment) +Maximum gas for the `deploy_account` transaction. When not used, defaults to auto-estimation. Must be greater than zero. (Only for STRK fee payment) ## ` --max-gas-unit-price ` Optional. -Maximum gas unit price for the `deploy_account` transaction paid in Fri. When not used, defaults to auto-estimation. (Only for STRK fee payment) +Maximum gas unit price for the `deploy_account` transaction paid in Fri. When not used, defaults to auto-estimation. Must be greater than zero. (Only for STRK fee payment) ## `--version, -v ` Optional. When not used, defaults to v3. diff --git a/docs/src/appendix/sncast/declare.md b/docs/src/appendix/sncast/declare.md index 28247ee49b..7ee362515c 100644 --- a/docs/src/appendix/sncast/declare.md +++ b/docs/src/appendix/sncast/declare.md @@ -20,7 +20,7 @@ Overrides url from `snfoundry.toml`. ## `--max-fee, -m ` Optional. -Maximum fee for the `declare` transaction in Fri or Wei depending on fee token or transaction version. When not used, defaults to auto-estimation. +Maximum fee for the `declare` transaction in Fri or Wei depending on fee token or transaction version. When not used, defaults to auto-estimation. Must be greater than zero. ## `--fee-token ` Optional. When not used, defaults to STRK. @@ -30,12 +30,12 @@ Token used for fee payment. Possible values: ETH, STRK. ## `--max-gas ` Optional. -Maximum gas for the `declare` transaction. When not used, defaults to auto-estimation. (Only for STRK fee payment) +Maximum gas for the `declare` transaction. When not used, defaults to auto-estimation. Must be greater than zero. (Only for STRK fee payment) ## ` --max-gas-unit-price ` Optional. -Maximum gas unit price for the `declare` transaction paid in Fri. When not used, defaults to auto-estimation. (Only for STRK fee payment) +Maximum gas unit price for the `declare` transaction paid in Fri. When not used, defaults to auto-estimation. Must be greater than zero. (Only for STRK fee payment) ## `--version, -v ` Optional. When not used, defaults to v3. diff --git a/docs/src/appendix/sncast/deploy.md b/docs/src/appendix/sncast/deploy.md index f5a611cdb7..f10c16185c 100644 --- a/docs/src/appendix/sncast/deploy.md +++ b/docs/src/appendix/sncast/deploy.md @@ -35,7 +35,7 @@ If passed, the salt will be additionally modified with an account address. ## `--max-fee, -m ` Optional. -Maximum fee for the `deploy` transaction in Fri or Wei depending on fee token or transaction version. When not used, defaults to auto-estimation. +Maximum fee for the `deploy` transaction in Fri or Wei depending on fee token or transaction version. When not used, defaults to auto-estimation. Must be greater than zero. ## `--fee-token ` Optional. When not used, defaults to STRK. @@ -45,12 +45,12 @@ Token used for fee payment. Possible values: ETH, STRK. ## `--max-gas ` Optional. -Maximum gas for the `deploy` transaction. When not used, defaults to auto-estimation. (Only for STRK fee payment) +Maximum gas for the `deploy` transaction. When not used, defaults to auto-estimation. Must be greater than zero. (Only for STRK fee payment) ## ` --max-gas-unit-price ` Optional. -Maximum gas unit price for the `deploy` transaction paid in Fri. When not used, defaults to auto-estimation. (Only for STRK fee payment) +Maximum gas unit price for the `deploy` transaction paid in Fri. When not used, defaults to auto-estimation. Must be greater than zero. (Only for STRK fee payment) ## `--version, -v ` Optional. When not used, defaults to v3. diff --git a/docs/src/appendix/sncast/invoke.md b/docs/src/appendix/sncast/invoke.md index d5922648a3..313dee972d 100644 --- a/docs/src/appendix/sncast/invoke.md +++ b/docs/src/appendix/sncast/invoke.md @@ -31,7 +31,7 @@ Overrides url from `snfoundry.toml`. ## `--max-fee, -m ` Optional. -Maximum fee for the `invoke` transaction in Fri or Wei depending on fee token or transaction version. When not used, defaults to auto-estimation. +Maximum fee for the `invoke` transaction in Fri or Wei depending on fee token or transaction version. When not used, defaults to auto-estimation. Must be greater than zero. ## `--fee-token ` Optional. When not used, defaults to STRK. @@ -41,12 +41,12 @@ Token used for fee payment. Possible values: ETH, STRK. ## `--max-gas ` Optional. -Maximum gas for the `invoke` transaction. When not used, defaults to auto-estimation. (Only for STRK fee payment) +Maximum gas for the `invoke` transaction. When not used, defaults to auto-estimation. Must be greater than zero. (Only for STRK fee payment) ## ` --max-gas-unit-price ` Optional. -Maximum gas unit price for the `invoke` transaction paid in Fri. When not used, defaults to auto-estimation. (Only for STRK fee payment) +Maximum gas unit price for the `invoke` transaction paid in Fri. When not used, defaults to auto-estimation. Must be greater than zero. (Only for STRK fee payment) ## `--version, -v ` Optional. When not used, defaults to v3. diff --git a/docs/src/appendix/sncast/multicall/run.md b/docs/src/appendix/sncast/multicall/run.md index ad481c2833..b2a5ae4809 100644 --- a/docs/src/appendix/sncast/multicall/run.md +++ b/docs/src/appendix/sncast/multicall/run.md @@ -21,7 +21,7 @@ Overrides url from `snfoundry.toml`. ## `--max-fee, -m ` Optional. -Maximum fee for the `invoke` transaction in Fri or Wei depending on fee token or transaction version. When not used, defaults to auto-estimation. +Maximum fee for the `invoke` transaction in Fri or Wei depending on fee token or transaction version. When not used, defaults to auto-estimation. Must be greater than zero. ## `--fee-token ` Optional. When not used, defaults to STRK. @@ -31,12 +31,12 @@ Token used for fee payment. Possible values: ETH, STRK. ## `--max-gas ` Optional. -Maximum gas for the `invoke` transaction. When not used, defaults to auto-estimation. (Only for STRK fee payment) +Maximum gas for the `invoke` transaction. When not used, defaults to auto-estimation. Must be greater than zero. (Only for STRK fee payment) ## ` --max-gas-unit-price ` Optional. -Maximum gas unit price for the `invoke` transaction paid in Fri. When not used, defaults to auto-estimation. (Only for STRK fee payment) +Maximum gas unit price for the `invoke` transaction paid in Fri. When not used, defaults to auto-estimation. Must be greater than zero. (Only for STRK fee payment) ## `--version, -v ` Optional. When not used, defaults to v3. diff --git a/scripts/snfoundryup b/scripts/snfoundryup index a87c8a83e7..9d90cd1523 100755 --- a/scripts/snfoundryup +++ b/scripts/snfoundryup @@ -47,7 +47,7 @@ Read the docs: Join the community: - Follow core developers on X: https://twitter.com/swmansionxyz - Get support via Telegram: https://t.me/starknet_foundry_support -- Or discord: https://discord.gg/KZWaFtPZJf +- Or discord: https://discord.gg/starknet-community - Or join our general chat (Telegram): https://t.me/starknet_foundry Report bugs: https://github.com/foundry-rs/starknet-foundry/issues/new/choose diff --git a/sncast_std/Scarb.lock b/sncast_std/Scarb.lock index 152c497386..63d1258262 100644 --- a/sncast_std/Scarb.lock +++ b/sncast_std/Scarb.lock @@ -3,4 +3,4 @@ version = 1 [[package]] name = "sncast_std" -version = "0.35.1" +version = "0.36.0" diff --git a/sncast_std/Scarb.toml b/sncast_std/Scarb.toml index c4ab7404a6..8859464d47 100644 --- a/sncast_std/Scarb.toml +++ b/sncast_std/Scarb.toml @@ -1,6 +1,6 @@ [package] name = "sncast_std" -version = "0.35.1" +version = "0.36.0" edition = "2023_11" description = "Library used for writing deployment scripts in Cairo" homepage = "https://foundry-rs.github.io/starknet-foundry/starknet/script.html" diff --git a/snforge_std/Scarb.lock b/snforge_std/Scarb.lock index 1f03b8c0a3..d895dcf867 100644 --- a/snforge_std/Scarb.lock +++ b/snforge_std/Scarb.lock @@ -3,11 +3,11 @@ version = 1 [[package]] name = "snforge_scarb_plugin" -version = "0.35.1" +version = "0.36.0" [[package]] name = "snforge_std" -version = "0.35.1" +version = "0.36.0" dependencies = [ "snforge_scarb_plugin", ] diff --git a/snforge_std/Scarb.toml b/snforge_std/Scarb.toml index d8a1cf098c..bd1e9d2d7c 100644 --- a/snforge_std/Scarb.toml +++ b/snforge_std/Scarb.toml @@ -1,6 +1,6 @@ [package] name = "snforge_std" -version = "0.35.1" +version = "0.36.0" edition = "2024_07" description = "Cairo testing library" documentation = "https://foundry-rs.github.io/starknet-foundry/appendix/snforge-library.html"