diff --git a/Cargo.lock b/Cargo.lock index eed8fb4f9b..45a875bd35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -430,6 +430,7 @@ dependencies = [ "num-rational", "num-traits 0.2.19", "once_cell", + "paste", "phf", "pretty_assertions", "rand", @@ -2643,9 +2644,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "path-clean" diff --git a/Cargo.toml b/Cargo.toml index 103fa3da93..9722c3fd68 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ num-rational = { version = "0.4", features = ["serde"] } num-traits = "0.2" once_cell = "1.19.0" papyrus_storage = "0.4.0-dev.4" +paste = "1.0.15" phf = { version = "0.11", features = ["macros"] } pretty_assertions = "1.2.1" pyo3 = "0.19.1" diff --git a/crates/blockifier/Cargo.toml b/crates/blockifier/Cargo.toml index 6baafab7da..8516535f71 100644 --- a/crates/blockifier/Cargo.toml +++ b/crates/blockifier/Cargo.toml @@ -38,6 +38,7 @@ num-integer.workspace = true num-rational.workspace = true num-traits.workspace = true once_cell.workspace = true +paste.workspace = true phf.workspace = true rand = { workspace = true, optional = true } rstest = { workspace = true, optional = true } diff --git a/crates/blockifier/src/test_utils/struct_impls.rs b/crates/blockifier/src/test_utils/struct_impls.rs index 52cbd67adb..eedfcdc956 100644 --- a/crates/blockifier/src/test_utils/struct_impls.rs +++ b/crates/blockifier/src/test_utils/struct_impls.rs @@ -28,7 +28,7 @@ use crate::transaction::objects::{ DeprecatedTransactionInfo, FeeType, TransactionFeeResult, TransactionInfo, TransactionResources, }; use crate::versioned_constants::{ - GasCosts, OsConstants, VersionedConstants, DEFAULT_CONSTANTS_JSON, + GasCosts, OsConstants, VersionedConstants, VERSIONED_CONSTANTS_LATEST_JSON, }; impl CallEntryPoint { @@ -109,11 +109,12 @@ impl TransactionResources { impl GasCosts { pub fn create_for_testing_from_subset(subset_of_os_constants: &str) -> Self { let subset_of_os_constants: Value = serde_json::from_str(subset_of_os_constants).unwrap(); - let mut os_constants: Value = serde_json::from_str::(DEFAULT_CONSTANTS_JSON) - .unwrap() - .get("os_constants") - .unwrap() - .clone(); + let mut os_constants: Value = + serde_json::from_str::(VERSIONED_CONSTANTS_LATEST_JSON) + .unwrap() + .get("os_constants") + .unwrap() + .clone(); update_json_value(&mut os_constants, subset_of_os_constants); let os_constants: OsConstants = serde_json::from_value(os_constants).unwrap(); os_constants.gas_costs diff --git a/crates/blockifier/src/versioned_constants.rs b/crates/blockifier/src/versioned_constants.rs index 0114144755..c0c30b6536 100644 --- a/crates/blockifier/src/versioned_constants.rs +++ b/crates/blockifier/src/versioned_constants.rs @@ -8,10 +8,12 @@ use cairo_vm::vm::runners::cairo_runner::ExecutionResources; use indexmap::{IndexMap, IndexSet}; use num_rational::Ratio; use once_cell::sync::Lazy; +use paste::paste; use serde::de::Error as DeserializationError; use serde::{Deserialize, Deserializer}; use serde_json::{Map, Number, Value}; use strum::IntoEnumIterator; +use strum_macros::{EnumCount, EnumIter}; use thiserror::Error; use crate::execution::deprecated_syscalls::hint_processor::SyscallCounter; @@ -26,18 +28,56 @@ use crate::transaction::transaction_types::TransactionType; #[path = "versioned_constants_test.rs"] pub mod test; -pub(crate) const DEFAULT_CONSTANTS_JSON: &str = - include_str!("../resources/versioned_constants.json"); -static DEFAULT_CONSTANTS: Lazy = Lazy::new(|| { - serde_json::from_str(DEFAULT_CONSTANTS_JSON) - .expect("Versioned constants JSON file is malformed") -}); +/// Auto-generate getters for listed versioned constants versions. +macro_rules! define_versioned_constants { + ($(($variant:ident, $path_to_json:expr)),* $(,)?) => { + /// Enum of all the Starknet versions supporting versioned constants. + #[derive(Clone, Debug, EnumCount, EnumIter, Hash, Eq, PartialEq)] + pub enum StarknetVersion { + $($variant,)* + } + + // Static (lazy) instances of the versioned constants. + // For internal use only; for access to a static instance use the `StarknetVersion` enum. + paste! { + $( + pub(crate) const []: &str = + include_str!($path_to_json); + static []: Lazy = Lazy::new(|| { + serde_json::from_str([]) + .expect(&format!("Versioned constants {} is malformed.", $path_to_json)) + }); + )* + } + + /// API to access a static instance of the versioned constants. + impl From for &'static VersionedConstants { + fn from(version: StarknetVersion) -> Self { + match version { + $( + StarknetVersion::$variant => { + & paste! { [] } + } + )* + } + } + } + }; +} + +define_versioned_constants! { + (V0_13_0, "../resources/versioned_constants_13_0.json"), + (V0_13_1, "../resources/versioned_constants_13_1.json"), + (V0_13_1_1, "../resources/versioned_constants_13_1_1.json"), + (Latest, "../resources/versioned_constants.json"), +} pub type ResourceCost = Ratio; /// Contains constants for the Blockifier that may vary between versions. /// Additional constants in the JSON file, not used by Blockifier but included for transparency, are /// automatically ignored during deserialization. +/// Instances of this struct for specific Starknet versions can be selected by using the above enum. #[derive(Clone, Debug, Default, Deserialize)] pub struct VersionedConstants { // Limits. @@ -68,10 +108,15 @@ pub struct VersionedConstants { } impl VersionedConstants { + /// Get the constants for the specified Starknet version. + pub fn get(version: StarknetVersion) -> &'static Self { + version.into() + } + /// Get the constants that shipped with the current version of the Blockifier. /// To use custom constants, initialize the struct from a file using `try_from`. pub fn latest_constants() -> &'static Self { - &DEFAULT_CONSTANTS + Self::get(StarknetVersion::Latest) } /// Returns the initial gas of any transaction to run with. diff --git a/crates/blockifier/src/versioned_constants_test.rs b/crates/blockifier/src/versioned_constants_test.rs index db1fa7a6f1..ff6dd2d4d2 100644 --- a/crates/blockifier/src/versioned_constants_test.rs +++ b/crates/blockifier/src/versioned_constants_test.rs @@ -1,5 +1,5 @@ use cairo_vm::types::builtin_name::BuiltinName; -use glob::glob; +use glob::{glob, Paths}; use pretty_assertions::assert_eq; use super::*; @@ -7,6 +7,11 @@ use super::*; // TODO: Test Starknet OS validation. // TODO: Add an unallowed field scenario for GasCost parsing. +/// Returns all JSON files in the resources directory (should be all versioned constants files). +fn all_jsons_in_dir() -> Paths { + glob(format!("{}/resources/*.json", env!("CARGO_MANIFEST_DIR")).as_str()).unwrap() +} + #[test] fn test_successful_gas_costs_parsing() { let json_data = r#" @@ -70,7 +75,7 @@ fn get_json_value_without_defaults() -> serde_json::Value { "max_recursion_depth": 2 }"#; // Fill the os constants with the gas cost values (do not have a default value). - let mut os_constants: Value = serde_json::from_str::(DEFAULT_CONSTANTS_JSON) + let mut os_constants: Value = serde_json::from_str::(VERSIONED_CONSTANTS_LATEST_JSON) .unwrap() .get("os_constants") .unwrap() @@ -91,7 +96,7 @@ fn get_json_value_without_defaults() -> serde_json::Value { #[test] fn test_versioned_constants_base_overrides() { // Create a versioned constants copy with a modified value for `invoke_tx_max_n_steps`. - let mut versioned_constants_base_overrides = DEFAULT_CONSTANTS.clone(); + let mut versioned_constants_base_overrides = VERSIONED_CONSTANTS_LATEST.clone(); versioned_constants_base_overrides.invoke_tx_max_n_steps += 1; let result = VersionedConstants::get_versioned_constants(VersionedConstantsOverrides { @@ -218,9 +223,13 @@ fn test_invalid_number() { #[test] fn test_old_json_parsing() { - let files = glob(format!("{}/resources/*.json", env!("CARGO_MANIFEST_DIR")).as_str()).unwrap(); - for file in files.map(Result::unwrap) { + for file in all_jsons_in_dir().map(Result::unwrap) { serde_json::from_reader::<_, VersionedConstants>(&std::fs::File::open(&file).unwrap()) .unwrap_or_else(|_| panic!("Versioned constants JSON file {file:#?} is malformed")); } } + +#[test] +fn test_all_jsons_in_enum() { + assert_eq!(StarknetVersion::iter().count(), all_jsons_in_dir().count()); +}