diff --git a/crates/forge/src/run_tests/resolve_config.rs b/crates/forge/src/run_tests/resolve_config.rs index 54b4d14118..6b57852023 100644 --- a/crates/forge/src/run_tests/resolve_config.rs +++ b/crates/forge/src/run_tests/resolve_config.rs @@ -11,7 +11,6 @@ use forge_runner::package_tests::{ TestTargetWithResolvedConfig, }, }; -use num_bigint::BigInt; use starknet_api::block::BlockNumber; pub async fn resolve_config( @@ -78,27 +77,6 @@ async fn resolve_fork_config( Ok(Some(ResolvedForkConfig { url, block_number })) } -fn parse_block_id(fork_target: &ForkTarget) -> Result { - let block_id = match fork_target.block_id_type.as_str() { - "number" => BlockId::BlockNumber(fork_target.block_id_value.parse()?), - "hash" => { - let block_hash = fork_target.block_id_value.parse::()?; - - BlockId::BlockHash(block_hash.into()) - } - "tag" => { - if fork_target.block_id_value == "latest" { - BlockId::BlockTag - } else { - Err(anyhow!(r#"only "latest" block tag is supported"#))? - } - } - _ => Err(anyhow!("block_id must be one of (number | hash | tag)"))?, - }; - - Ok(block_id) -} - fn get_fork_target_from_runner_config<'a>( fork_targets: &'a [ForkTarget], name: &ByteArray, @@ -122,10 +100,10 @@ fn replace_id_with_params( let fork_target_from_runner_config = get_fork_target_from_runner_config(fork_targets, &name)?; - let block_id = parse_block_id(fork_target_from_runner_config)?; + let block_id = fork_target_from_runner_config.block_id.clone(); Ok(InlineForkConfig { - url: fork_target_from_runner_config.url.parse()?, + url: fork_target_from_runner_config.url.clone(), block: block_id, }) } @@ -133,7 +111,7 @@ fn replace_id_with_params( let fork_target_from_runner_config = get_fork_target_from_runner_config(fork_targets, &name)?; - let url = fork_target_from_runner_config.url.parse()?; + let url = fork_target_from_runner_config.url.clone(); Ok(InlineForkConfig { url, block }) } @@ -163,50 +141,6 @@ mod tests { } } - #[tokio::test] - async fn to_runnable_unparsable_url() { - let mocked_tests = TestTargetWithConfig { - sierra_program: program_for_testing(), - casm_program: Arc::new(compile_sierra_to_casm(&program_for_testing().program).unwrap()), - test_cases: vec![TestCaseWithConfig { - name: "crate1::do_thing".to_string(), - config: TestCaseConfig { - available_gas: None, - ignored: false, - expected_result: ExpectedTestResult::Success, - fork_config: Some(RawForkConfig::Named("SOME_NAME".into())), - fuzzer_config: None, - }, - test_details: TestDetails { - sierra_entry_point_statement_idx: 100, - parameter_types: vec![ - (GenericTypeId("RangeCheck".into()), 1), - (GenericTypeId("GasBuiltin".into()), 1), - ], - return_types: vec![ - (GenericTypeId("RangeCheck".into()), 1), - (GenericTypeId("GasBuiltin".into()), 1), - (GenericTypeId("Enum".into()), 3), - ], - }, - }], - tests_location: TestTargetLocation::Lib, - }; - - assert!(resolve_config( - mocked_tests, - &[ForkTarget { - name: "SOME_NAME".to_string(), - url: "unparsable_url".to_string(), - block_id_type: "Tag".to_string(), - block_id_value: "Latest".to_string(), - }], - &mut BlockNumberMap::default() - ) - .await - .is_err()); - } - #[tokio::test] async fn to_runnable_non_existent_id() { let mocked_tests = TestTargetWithConfig { @@ -240,11 +174,12 @@ mod tests { assert!(resolve_config( mocked_tests, &[ForkTarget::new( - "definitely_non_existing".to_string(), - "https://not_taken.com".to_string(), - "Number".to_string(), - "120".to_string(), - )], + "definitely_non_existing", + "https://not_taken.com", + "number", + "120", + ) + .unwrap()], &mut BlockNumberMap::default() ) .await diff --git a/crates/forge/src/scarb.rs b/crates/forge/src/scarb.rs index b49a701bc3..0215cb2e4c 100644 --- a/crates/forge/src/scarb.rs +++ b/crates/forge/src/scarb.rs @@ -145,6 +145,7 @@ mod tests { use assert_fs::fixture::{FileWriteStr, PathChild, PathCopy}; use assert_fs::TempDir; use camino::Utf8PathBuf; + use cheatnet::runtime_extensions::forge_config_extension::config::BlockId; use configuration::load_package_config; use indoc::{formatdoc, indoc}; use scarb_api::metadata::MetadataCommandExt; @@ -188,11 +189,16 @@ mod tests { [[tool.snforge.fork]] name = "SECOND_FORK_NAME" url = "http://some.rpc.url" - block_id.hash = "1" + block_id.hash = "0xa" [[tool.snforge.fork]] name = "THIRD_FORK_NAME" url = "http://some.rpc.url" + block_id.hash = "10" + + [[tool.snforge.fork]] + name = "FOURTH_FORK_NAME" + url = "http://some.rpc.url" block_id.tag = "latest" "#, package_name, @@ -223,24 +229,14 @@ mod tests { ForgeConfigFromScarb { exit_first: false, fork: vec![ - ForkTarget::new( - "FIRST_FORK_NAME".to_string(), - "http://some.rpc.url".to_string(), - "number".to_string(), - "1".to_string(), - ), - ForkTarget::new( - "SECOND_FORK_NAME".to_string(), - "http://some.rpc.url".to_string(), - "hash".to_string(), - "1".to_string(), - ), - ForkTarget::new( - "THIRD_FORK_NAME".to_string(), - "http://some.rpc.url".to_string(), - "tag".to_string(), - "latest".to_string(), - ) + ForkTarget::new("FIRST_FORK_NAME", "http://some.rpc.url", "number", "1",) + .unwrap(), + ForkTarget::new("SECOND_FORK_NAME", "http://some.rpc.url", "hash", "10",) + .unwrap(), + ForkTarget::new("THIRD_FORK_NAME", "http://some.rpc.url", "hash", "0xa",) + .unwrap(), + ForkTarget::new("FOURTH_FORK_NAME", "http://some.rpc.url", "tag", "latest",) + .unwrap() ], fuzzer_runs: None, fuzzer_seed: None, @@ -431,6 +427,37 @@ mod tests { assert!(format!("{err:?}").contains("block_id.tag can only be equal to latest")); } + #[test] + fn get_forge_config_for_package_with_block_tag() { + let temp = setup_package("simple_package"); + let content = indoc!( + r#" + [package] + name = "simple_package" + version = "0.1.0" + + [[tool.snforge.fork]] + name = "SAME_NAME" + url = "http://some.rpc.url" + block_id.tag = "latest" + "# + ); + temp.child("Scarb.toml").write_str(content).unwrap(); + + let scarb_metadata = ScarbCommand::metadata() + .inherit_stderr() + .current_dir(temp.path()) + .run() + .unwrap(); + + let forge_config = load_package_config::( + &scarb_metadata, + &scarb_metadata.workspace.members[0], + ) + .unwrap(); + assert_eq!(forge_config.fork[0].block_id, BlockId::BlockTag); + } + #[test] fn get_forge_config_resolves_env_variables() { let temp = setup_package("simple_package"); @@ -466,11 +493,12 @@ mod tests { ForgeConfigFromScarb { exit_first: false, fork: vec![ForkTarget::new( - "ENV_URL_FORK".to_string(), - "http://some.rpc.url_from_env".to_string(), - "number".to_string(), - "1".to_string(), - )], + "ENV_URL_FORK", + "http://some.rpc.url_from_env", + "number", + "1", + ) + .unwrap()], fuzzer_runs: None, fuzzer_seed: None, max_n_steps: None, diff --git a/crates/forge/src/scarb/config.rs b/crates/forge/src/scarb/config.rs index 0940c3d582..dbf0d3acde 100644 --- a/crates/forge/src/scarb/config.rs +++ b/crates/forge/src/scarb/config.rs @@ -1,10 +1,12 @@ -use anyhow::{bail, Result}; +use anyhow::{anyhow, bail, Result}; +use cheatnet::runtime_extensions::forge_config_extension::config::BlockId; use itertools::Itertools; use serde::Deserialize; use std::{ collections::{HashMap, HashSet}, num::NonZeroU32, }; +use url::Url; #[allow(clippy::module_name_repetitions)] #[allow(clippy::struct_excessive_bools)] @@ -34,20 +36,36 @@ pub struct ForgeConfigFromScarb { #[derive(Debug, PartialEq, Clone)] pub struct ForkTarget { pub name: String, - pub url: String, - pub block_id_type: String, - pub block_id_value: String, + pub url: Url, + pub block_id: BlockId, } impl ForkTarget { - #[must_use] - pub fn new(name: String, url: String, block_id_type: String, block_id_value: String) -> Self { - Self { - name, - url, - block_id_type, - block_id_value, - } + pub fn new(name: &str, url: &str, block_id_type: &str, block_id_value: &str) -> Result { + let parsed_url = Url::parse(url).map_err(|_| anyhow!("Failed to parse fork url"))?; + let block_id = match block_id_type { + "number" => BlockId::BlockNumber( + block_id_value + .parse() + .map_err(|_| anyhow!("Failed to parse block number"))?, + ), + "hash" => BlockId::BlockHash( + block_id_value + .parse() + .map_err(|_| anyhow!("Failed to parse block hash"))?, + ), + "tag" => match block_id_value { + "latest" => BlockId::BlockTag, + _ => bail!("block_id.tag can only be equal to latest"), + }, + block_id_key => bail!("block_id = {block_id_key} is not valid. Possible values are = \"number\", \"hash\" and \"tag\""), + }; + + Ok(Self { + name: name.to_string(), + url: parsed_url, + block_id, + }) } } @@ -98,21 +116,12 @@ fn validate_raw_fork_config(raw_config: RawForgeConfig) -> Result Ok(()), + _ => bail!("block_id should be set once per fork"), + })?; Ok(raw_config) } @@ -129,11 +138,11 @@ impl TryFrom for ForgeConfigFromScarb { raw_fork_target.block_id.iter().exactly_one().unwrap(); fork_targets.push(ForkTarget::new( - raw_fork_target.name, - raw_fork_target.url, - block_id_type.to_string(), - block_id_value.clone(), - )); + raw_fork_target.name.as_str(), + raw_fork_target.url.as_str(), + block_id_type, + block_id_value, + )?); } Ok(ForgeConfigFromScarb { @@ -149,3 +158,106 @@ impl TryFrom for ForgeConfigFromScarb { }) } } + +#[cfg(test)] +mod tests { + use super::*; + use num_bigint::BigInt; + use url::Url; + + #[test] + fn test_fork_target_new_valid_number() { + let name = "TestFork"; + let url = "http://example.com"; + let block_id_type = "number"; + let block_id_value = "123"; + + let fork_target = ForkTarget::new(name, url, block_id_type, block_id_value).unwrap(); + + assert_eq!(fork_target.name, name); + assert_eq!(fork_target.url, Url::parse(url).unwrap()); + if let BlockId::BlockNumber(number) = fork_target.block_id { + assert_eq!(number, 123); + } else { + panic!("Expected BlockId::BlockNumber"); + } + } + + #[test] + fn test_fork_target_new_valid_hash() { + let name = "TestFork"; + let url = "http://example.com"; + let block_id_type = "hash"; + let block_id_value = "0x1"; + + let fork_target = ForkTarget::new(name, url, block_id_type, block_id_value).unwrap(); + + assert_eq!(fork_target.name, name); + assert_eq!(fork_target.url, Url::parse(url).unwrap()); + if let BlockId::BlockHash(hash) = fork_target.block_id { + assert_eq!(hash.to_bigint(), BigInt::from(1)); + } else { + panic!("Expected BlockId::BlockHash"); + } + } + + #[test] + fn test_fork_target_new_valid_tag() { + let name = "TestFork"; + let url = "http://example.com"; + let block_id_type = "tag"; + let block_id_value = "latest"; + + let fork_target = ForkTarget::new(name, url, block_id_type, block_id_value).unwrap(); + + assert_eq!(fork_target.name, name); + assert_eq!(fork_target.url, Url::parse(url).unwrap()); + if let BlockId::BlockTag = fork_target.block_id { + // Expected variant + } else { + panic!("Expected BlockId::BlockTag"); + } + } + + #[test] + fn test_fork_target_new_invalid_url() { + let name = "TestFork"; + let url = "invalid_url"; + let block_id_type = "number"; + let block_id_value = "123"; + + let result = ForkTarget::new(name, url, block_id_type, block_id_value); + assert!(result.is_err()); + assert_eq!(result.unwrap_err().to_string(), "Failed to parse fork url"); + } + + #[test] + fn test_fork_target_new_invalid_block_id_value_number() { + let name = "TestFork"; + let url = "http://example.com"; + let block_id_type = "number"; + let block_id_value = "invalid_number"; + + let result = ForkTarget::new(name, url, block_id_type, block_id_value); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "Failed to parse block number" + ); + } + + #[test] + fn test_fork_target_new_invalid_block_id_value_hash() { + let name = "TestFork"; + let url = "http://example.com"; + let block_id_type = "hash"; + let block_id_value = "invalid_hash"; + + let result = ForkTarget::new(name, url, block_id_type, block_id_value); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "Failed to parse block hash" + ); + } +} diff --git a/crates/forge/tests/integration/setup_fork.rs b/crates/forge/tests/integration/setup_fork.rs index cf751b523b..06ce5b4426 100644 --- a/crates/forge/tests/integration/setup_fork.rs +++ b/crates/forge/tests/integration/setup_fork.rs @@ -165,11 +165,12 @@ fn fork_aliased_decorator() { }), }), fork_targets: vec![ForkTarget::new( - "FORK_NAME_FROM_SCARB_TOML".to_string(), - node_rpc_url().to_string(), - "tag".to_string(), - "latest".to_string(), - )], + "FORK_NAME_FROM_SCARB_TOML", + node_rpc_url().as_str(), + "tag", + "latest", + ) + .unwrap()], }, &mut BlockNumberMap::default(), )) @@ -256,11 +257,12 @@ fn fork_aliased_decorator_overrding() { }), }), fork_targets: vec![ForkTarget::new( - "FORK_NAME_FROM_SCARB_TOML".to_string(), - node_rpc_url().to_string(), - "number".to_string(), - "12341234".to_string(), - )], + "FORK_NAME_FROM_SCARB_TOML", + node_rpc_url().as_str(), + "number", + "12341234", + ) + .unwrap()], }, &mut BlockNumberMap::default(), )) @@ -366,6 +368,26 @@ fn get_block_info_in_forked_block() { assert(block_info.block_timestamp > 1711645884, block_info.block_timestamp.into()); assert(block_info.block_number > 54060, block_info.block_number.into()); }} + + #[test] + #[fork(url: "{node_rpc_url}", block_hash: 0x06ae121e46f5375f93b00475fb130348ae38148e121f84b0865e17542e9485de)] + fn test_fork_get_block_info_block_hash() {{ + let block_info = starknet::get_block_info().unbox(); + assert(block_info.block_timestamp == 1711645884, block_info.block_timestamp.into()); + assert(block_info.block_number == 54060, block_info.block_number.into()); + let expected_sequencer_addr = contract_address_const::<0x1176a1bd84444c89232ec27754698e5d2e7e1a7f1539f12027f28b23ec9f3d8>(); + assert(block_info.sequencer_address == expected_sequencer_addr, block_info.sequencer_address.into()); + }} + + #[test] + #[fork(url: "{node_rpc_url}", block_hash: 3021433528476416000728121069095289682281028310523383289416465162415092565470)] + fn test_fork_get_block_info_block_hash_with_number() {{ + let block_info = starknet::get_block_info().unbox(); + assert(block_info.block_timestamp == 1711645884, block_info.block_timestamp.into()); + assert(block_info.block_number == 54060, block_info.block_number.into()); + let expected_sequencer_addr = contract_address_const::<0x1176a1bd84444c89232ec27754698e5d2e7e1a7f1539f12027f28b23ec9f3d8>(); + assert(block_info.sequencer_address == expected_sequencer_addr, block_info.sequencer_address.into()); + }} "#, node_rpc_url = node_rpc_url() ).as_str(), diff --git a/crates/scarb-api/src/lib.rs b/crates/scarb-api/src/lib.rs index 60585b96c2..2082356ad1 100644 --- a/crates/scarb-api/src/lib.rs +++ b/crates/scarb-api/src/lib.rs @@ -267,7 +267,7 @@ mod tests { [[tool.snforge.fork]] name = "THIRD_FORK_NAME" url = "http://some.rpc.url" - block_id.tag = "Latest" + block_id.tag = "latest" "#, package_name, snforge_std_path