From 426d245dd969e0a15bf4be0f4cc4bbc1d892d24c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artur=20Micha=C5=82ek?= <52135326+cptartur@users.noreply.github.com> Date: Wed, 23 Oct 2024 17:17:37 +0200 Subject: [PATCH] Get features from all targets - refactor (#2600) Closes # ## Introduced changes Part of stack Base: https://github.com/foundry-rs/starknet-foundry/pull/2570 ## Checklist - [x] Linked relevant issue - [x] Updated relevant documentation - [x] Added relevant tests - [x] Performed self-review of the code - [x] Added changes to `CHANGELOG.md` --- Cargo.lock | 1 + crates/scarb-api/Cargo.toml | 1 + crates/scarb-api/src/artifacts.rs | 4 + crates/scarb-api/src/artifacts/artifacts.rs | 89 ++++++++ .../scarb-api/src/artifacts/deserialized.rs | 39 ++++ .../scarb-api/src/artifacts/representation.rs | 78 +++++++ crates/scarb-api/src/lib.rs | 210 +++--------------- 7 files changed, 242 insertions(+), 180 deletions(-) create mode 100644 crates/scarb-api/src/artifacts.rs create mode 100644 crates/scarb-api/src/artifacts/artifacts.rs create mode 100644 crates/scarb-api/src/artifacts/deserialized.rs create mode 100644 crates/scarb-api/src/artifacts/representation.rs diff --git a/Cargo.lock b/Cargo.lock index 106102c04..115ea7361 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4295,6 +4295,7 @@ dependencies = [ "assert_fs", "camino", "indoc", + "itertools 0.12.1", "rayon", "regex", "scarb-metadata", diff --git a/crates/scarb-api/Cargo.toml b/crates/scarb-api/Cargo.toml index d0a1cdc89..e2d811603 100644 --- a/crates/scarb-api/Cargo.toml +++ b/crates/scarb-api/Cargo.toml @@ -19,6 +19,7 @@ which.workspace = true semver.workspace = true regex.workspace = true rayon.workspace = true +itertools.workspace = true universal-sierra-compiler-api = { path = "../universal-sierra-compiler-api" } [dev-dependencies] diff --git a/crates/scarb-api/src/artifacts.rs b/crates/scarb-api/src/artifacts.rs new file mode 100644 index 000000000..7ae5b4b40 --- /dev/null +++ b/crates/scarb-api/src/artifacts.rs @@ -0,0 +1,4 @@ +#[allow(clippy::module_inception)] +pub mod artifacts; +pub mod deserialized; +pub mod representation; diff --git a/crates/scarb-api/src/artifacts/artifacts.rs b/crates/scarb-api/src/artifacts/artifacts.rs new file mode 100644 index 000000000..6aead10fc --- /dev/null +++ b/crates/scarb-api/src/artifacts/artifacts.rs @@ -0,0 +1,89 @@ +use anyhow::Result; + +use crate::artifacts::representation::StarknetArtifactsRepresentation; +use camino::{Utf8Path, Utf8PathBuf}; +use itertools::Itertools; +use rayon::iter::{IntoParallelIterator, ParallelIterator}; +use std::collections::HashMap; +use std::fs; +use universal_sierra_compiler_api::{compile_sierra_at_path, SierraType}; + +/// Contains compiled Starknet artifacts +#[derive(Debug, PartialEq, Clone)] +pub struct StarknetContractArtifacts { + /// Compiled sierra code + pub sierra: String, + /// Compiled casm code + pub casm: String, +} + +#[derive(PartialEq, Debug)] +pub(crate) struct StarknetArtifactsFiles { + base_file: Utf8PathBuf, + other_files: Vec, +} + +impl StarknetArtifactsFiles { + pub(crate) fn new(base_file: Utf8PathBuf, other_files: Vec) -> Self { + Self { + base_file, + other_files, + } + } + + pub(crate) fn load_contracts_artifacts( + self, + ) -> Result> { + let mut base_artifacts: HashMap = + compile_artifacts( + StarknetArtifactsRepresentation::try_from_path(self.base_file.as_path())? + .artifacts(), + )?; + + let other_artifact_representations: Vec = self + .other_files + .iter() + .map(|path| StarknetArtifactsRepresentation::try_from_path(path.as_path())) + .collect::>()?; + + let other_artifacts: Vec<(String, Utf8PathBuf)> = + unique_artifacts(other_artifact_representations, &base_artifacts); + + let compiled_artifacts = compile_artifacts(other_artifacts)?; + + base_artifacts.extend(compiled_artifacts); + + Ok(base_artifacts) + } +} + +fn unique_artifacts( + artifact_representations: Vec, + current_artifacts: &HashMap, +) -> Vec<(String, Utf8PathBuf)> { + artifact_representations + .into_iter() + .flat_map(StarknetArtifactsRepresentation::artifacts) + .unique() + .filter(|(name, _)| !current_artifacts.contains_key(name)) + .collect() +} + +fn compile_artifacts( + artifacts: Vec<(String, Utf8PathBuf)>, +) -> Result> { + artifacts + .into_par_iter() + .map(|(name, path)| { + compile_artifact_at_path(&path).map(|artifact| (name.to_string(), (artifact, path))) + }) + .collect::>() +} + +fn compile_artifact_at_path(path: &Utf8Path) -> Result { + let sierra = fs::read_to_string(path)?; + + let casm = compile_sierra_at_path(path.as_str(), None, &SierraType::Contract)?; + + Ok(StarknetContractArtifacts { sierra, casm }) +} diff --git a/crates/scarb-api/src/artifacts/deserialized.rs b/crates/scarb-api/src/artifacts/deserialized.rs new file mode 100644 index 000000000..8dd292d6e --- /dev/null +++ b/crates/scarb-api/src/artifacts/deserialized.rs @@ -0,0 +1,39 @@ +use anyhow::{Context, Result}; +use camino::{Utf8Path, Utf8PathBuf}; +use serde::Deserialize; +use std::fs; + +#[derive(Deserialize, Debug, PartialEq, Clone)] +pub(super) struct StarknetArtifacts { + pub version: u32, + pub contracts: Vec, +} + +#[allow(dead_code)] +#[derive(Deserialize, Debug, PartialEq, Clone)] +pub(super) struct StarknetContract { + pub id: String, + pub package_name: String, + pub contract_name: String, + pub artifacts: StarknetContractArtifactPaths, +} + +#[allow(dead_code)] +#[derive(Deserialize, Debug, PartialEq, Clone)] +pub(super) struct StarknetContractArtifactPaths { + pub sierra: Utf8PathBuf, +} + +/// Get deserialized contents of `starknet_artifacts.json` file generated by Scarb +/// +/// # Arguments +/// +/// * `path` - A path to `starknet_artifacts.json` file. +pub(super) fn artifacts_for_package(path: &Utf8Path) -> Result { + let starknet_artifacts = + fs::read_to_string(path).with_context(|| format!("Failed to read {path:?} contents"))?; + let starknet_artifacts: StarknetArtifacts = + serde_json::from_str(starknet_artifacts.as_str()) + .with_context(|| format!("Failed to parse {path:?} contents. Make sure you have enabled sierra code generation in Scarb.toml"))?; + Ok(starknet_artifacts) +} diff --git a/crates/scarb-api/src/artifacts/representation.rs b/crates/scarb-api/src/artifacts/representation.rs new file mode 100644 index 000000000..4017d91b8 --- /dev/null +++ b/crates/scarb-api/src/artifacts/representation.rs @@ -0,0 +1,78 @@ +use crate::artifacts::deserialized::{artifacts_for_package, StarknetArtifacts}; +use anyhow::anyhow; +use camino::{Utf8Path, Utf8PathBuf}; + +pub(super) struct StarknetArtifactsRepresentation { + path: Utf8PathBuf, + artifacts: StarknetArtifacts, +} + +impl StarknetArtifactsRepresentation { + pub(super) fn try_from_path(path: &Utf8Path) -> anyhow::Result { + let artifacts = artifacts_for_package(path)?; + let path = path + .parent() + .ok_or_else(|| anyhow!("Failed to get parent for path = {}", &path))? + .to_path_buf(); + + Ok(Self { path, artifacts }) + } + + pub(super) fn artifacts(self) -> Vec<(String, Utf8PathBuf)> { + self.artifacts + .contracts + .into_iter() + .map(|contract| { + ( + contract.contract_name, + self.path.join(contract.artifacts.sierra.as_path()), + ) + }) + .collect() + } +} + +#[cfg(test)] +mod tests { + use crate::ScarbCommand; + use assert_fs::fixture::{FileTouch, FileWriteStr, PathChild, PathCopy}; + use assert_fs::TempDir; + use camino::Utf8PathBuf; + + use super::*; + + #[test] + fn parsing_starknet_artifacts() { + let temp = crate::tests::setup_package("basic_package"); + + ScarbCommand::new_with_stdio() + .current_dir(temp.path()) + .arg("build") + .run() + .unwrap(); + + let artifacts_path = temp + .path() + .join("target/dev/basic_package.starknet_artifacts.json"); + let artifacts_path = Utf8PathBuf::from_path_buf(artifacts_path).unwrap(); + + let artifacts = artifacts_for_package(&artifacts_path).unwrap(); + + assert!(!artifacts.contracts.is_empty()); + } + + #[test] + fn parsing_starknet_artifacts_on_invalid_file() { + let temp = TempDir::new().unwrap(); + temp.copy_from("../../", &[".tool-versions"]).unwrap(); + let path = temp.child("wrong.json"); + path.touch().unwrap(); + path.write_str("\"aa\": {}").unwrap(); + let artifacts_path = Utf8PathBuf::from_path_buf(path.to_path_buf()).unwrap(); + + let result = artifacts_for_package(&artifacts_path); + let err = result.unwrap_err(); + + assert!(err.to_string().contains(&format!("Failed to parse {artifacts_path:?} contents. Make sure you have enabled sierra code generation in Scarb.toml"))); + } +} diff --git a/crates/scarb-api/src/lib.rs b/crates/scarb-api/src/lib.rs index 3cf5d339a..aef7df540 100644 --- a/crates/scarb-api/src/lib.rs +++ b/crates/scarb-api/src/lib.rs @@ -1,112 +1,21 @@ -use anyhow::{anyhow, Context, Result}; +use anyhow::{anyhow, Result}; +use artifacts::artifacts::StarknetArtifactsFiles; use camino::{Utf8Path, Utf8PathBuf}; -use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; +pub use command::*; use scarb_metadata::{Metadata, PackageId, PackageMetadata, TargetMetadata}; use semver::VersionReq; -use serde::Deserialize; +use shared::print::print_as_warning; use std::collections::HashMap; -use std::fs; use std::str::FromStr; -use universal_sierra_compiler_api::{compile_sierra_at_path, SierraType}; - -pub use command::*; -use shared::print::print_as_warning; +mod artifacts; mod command; pub mod metadata; pub mod version; -const INTEGRATION_TEST_TYPE: &str = "integration"; - -#[derive(Deserialize, Debug, PartialEq, Clone)] -struct StarknetArtifacts { - version: u32, - contracts: Vec, -} - -#[allow(dead_code)] -#[derive(Deserialize, Debug, PartialEq, Clone)] -struct StarknetContract { - id: String, - package_name: String, - contract_name: String, - artifacts: StarknetContractArtifactPaths, -} - -#[allow(dead_code)] -#[derive(Deserialize, Debug, PartialEq, Clone)] -struct StarknetContractArtifactPaths { - sierra: Utf8PathBuf, -} - -/// Contains compiled Starknet artifacts -#[derive(Debug, PartialEq, Clone)] -pub struct StarknetContractArtifacts { - /// Compiled sierra code - pub sierra: String, - /// Compiled casm code - pub casm: String, -} +pub use crate::artifacts::artifacts::StarknetContractArtifacts; -impl StarknetContractArtifacts { - fn from_scarb_contract_artifact( - starknet_contract: &StarknetContract, - base_path: &Utf8Path, - ) -> Result { - let sierra_path = base_path.join(starknet_contract.artifacts.sierra.clone()); - let sierra = fs::read_to_string(sierra_path)?; - - let casm = compile_sierra_at_path( - starknet_contract.artifacts.sierra.as_str(), - Some(base_path.as_std_path()), - &SierraType::Contract, - )?; - - Ok(Self { sierra, casm }) - } -} - -/// Get deserialized contents of `starknet_artifacts.json` file generated by Scarb -/// -/// # Arguments -/// -/// * `path` - A path to `starknet_artifacts.json` file. -fn artifacts_for_package(path: &Utf8Path) -> Result { - let starknet_artifacts = - fs::read_to_string(path).with_context(|| format!("Failed to read {path:?} contents"))?; - let starknet_artifacts: StarknetArtifacts = - serde_json::from_str(starknet_artifacts.as_str()) - .with_context(|| format!("Failed to parse {path:?} contents. Make sure you have enabled sierra code generation in Scarb.toml"))?; - Ok(starknet_artifacts) -} - -#[derive(PartialEq, Debug)] -struct StarknetArtifactsFiles { - base_file: Utf8PathBuf, - other_files: Vec, -} - -impl StarknetArtifactsFiles { - fn load_contracts_artifacts( - self, - ) -> Result> { - let mut base_artifacts = load_contracts_artifacts_and_source_sierra_paths(&self.base_file)?; - - let compiled_artifacts = self - .other_files - .par_iter() - .map(load_contracts_artifacts_and_source_sierra_paths) - .collect::>>()?; - - for artifact in compiled_artifacts { - for (key, value) in artifact { - base_artifacts.entry(key).or_insert(value); - } - } - - Ok(base_artifacts) - } -} +const INTEGRATION_TEST_TYPE: &str = "integration"; /// Constructs `StarknetArtifactsFiles` from contracts built using test target. /// @@ -158,10 +67,10 @@ fn get_starknet_artifacts_paths_from_test_targets( .collect(); let (base_artifact_path, _) = base_artifact; - Some(StarknetArtifactsFiles { - base_file: base_artifact_path, - other_files: other_artifacts_paths, - }) + Some(StarknetArtifactsFiles::new( + base_artifact_path, + other_artifacts_paths, + )) } else { None } @@ -186,10 +95,7 @@ fn get_starknet_artifacts_path( None }; - path.map(|path| StarknetArtifactsFiles { - base_file: path, - other_files: vec![], - }) + path.map(|path| StarknetArtifactsFiles::new(path, vec![])) } /// Get the map with `StarknetContractArtifacts` for the given package @@ -219,27 +125,6 @@ pub fn get_contracts_artifacts_and_source_sierra_paths( } } -fn load_contracts_artifacts_and_source_sierra_paths( - contracts_path: &Utf8PathBuf, -) -> Result> { - let base_path = contracts_path - .parent() - .ok_or_else(|| anyhow!("Failed to get parent for path = {}", &contracts_path))?; - let artifacts = artifacts_for_package(contracts_path)?; - let mut map = HashMap::new(); - - for ref contract in artifacts.contracts { - let name = contract.contract_name.clone(); - let contract_artifacts = - StarknetContractArtifacts::from_scarb_contract_artifact(contract, base_path)?; - - let sierra_path = base_path.join(contract.artifacts.sierra.clone()); - - map.insert(name.clone(), (contract_artifacts, sierra_path)); - } - Ok(map) -} - #[must_use] pub fn target_dir_for_workspace(metadata: &Metadata) -> Utf8PathBuf { metadata @@ -302,13 +187,13 @@ mod tests { use super::*; use crate::metadata::MetadataCommandExt; use assert_fs::fixture::{FileWriteStr, PathChild, PathCopy}; - use assert_fs::prelude::FileTouch; use assert_fs::TempDir; use camino::Utf8PathBuf; use indoc::{formatdoc, indoc}; + use std::fs; use std::str::FromStr; - fn setup_package(package_name: &str) -> TempDir { + pub(crate) fn setup_package(package_name: &str) -> TempDir { let temp = TempDir::new().unwrap(); temp.copy_from( format!("tests/data/{package_name}"), @@ -379,14 +264,14 @@ mod tests { assert_eq!( path, - StarknetArtifactsFiles { - base_file: Utf8PathBuf::from_path_buf( + StarknetArtifactsFiles::new( + Utf8PathBuf::from_path_buf( temp.path() .join("target/dev/basic_package.starknet_artifacts.json") ) .unwrap(), - other_files: vec![] - } + vec![] + ) ); } @@ -421,14 +306,14 @@ mod tests { assert_eq!( path, - StarknetArtifactsFiles { - base_file: Utf8PathBuf::from_path_buf( + StarknetArtifactsFiles::new( + Utf8PathBuf::from_path_buf( temp.path() .join("target/dev/basic_package_unittest.test.starknet_artifacts.json") ) .unwrap(), - other_files: vec![], - } + vec![], + ) ); } @@ -476,19 +361,19 @@ mod tests { assert_eq!( path, - StarknetArtifactsFiles { - base_file: Utf8PathBuf::from_path_buf( + StarknetArtifactsFiles::new( + Utf8PathBuf::from_path_buf( temp.path().join( "target/dev/basic_package_integrationtest.test.starknet_artifacts.json" ) ) .unwrap(), - other_files: vec![Utf8PathBuf::from_path_buf( + vec![Utf8PathBuf::from_path_buf( temp.path() .join("target/dev/basic_package_unittest.test.starknet_artifacts.json") ) .unwrap(),] - }, + ), ); } @@ -586,13 +471,13 @@ mod tests { assert_eq!( path, - StarknetArtifactsFiles { - base_file: Utf8PathBuf::from_path_buf( + StarknetArtifactsFiles::new( + Utf8PathBuf::from_path_buf( temp.path().join("target/dev/essa.starknet_artifacts.json") ) .unwrap(), - other_files: vec![] - } + vec![] + ) ); } @@ -635,41 +520,6 @@ mod tests { assert!(path.is_none()); } - #[test] - fn parsing_starknet_artifacts() { - let temp = setup_package("basic_package"); - - ScarbCommand::new_with_stdio() - .current_dir(temp.path()) - .arg("build") - .run() - .unwrap(); - - let artifacts_path = temp - .path() - .join("target/dev/basic_package.starknet_artifacts.json"); - let artifacts_path = Utf8PathBuf::from_path_buf(artifacts_path).unwrap(); - - let artifacts = artifacts_for_package(&artifacts_path).unwrap(); - - assert!(!artifacts.contracts.is_empty()); - } - - #[test] - fn parsing_starknet_artifacts_on_invalid_file() { - let temp = TempDir::new().unwrap(); - temp.copy_from("../../", &[".tool-versions"]).unwrap(); - let path = temp.child("wrong.json"); - path.touch().unwrap(); - path.write_str("\"aa\": {}").unwrap(); - let artifacts_path = Utf8PathBuf::from_path_buf(path.to_path_buf()).unwrap(); - - let result = artifacts_for_package(&artifacts_path); - let err = result.unwrap_err(); - - assert!(err.to_string().contains(&format!("Failed to parse {artifacts_path:?} contents. Make sure you have enabled sierra code generation in Scarb.toml"))); - } - #[test] fn get_contracts() { let temp = setup_package("basic_package");