diff --git a/config/default_config.json b/config/default_config.json index 42fa1ecad..58732f860 100644 --- a/config/default_config.json +++ b/config/default_config.json @@ -9,6 +9,16 @@ "privacy": "Public", "value": true }, + "gateway_config.compiler_config.max_bytecode_size": { + "description": "Limitation of contract bytecode size", + "privacy": "Public", + "value": 81920 + }, + "gateway_config.compiler_config.max_raw_class_size": { + "description": "Limitation of contract class object size", + "privacy": "Public", + "value": 4089446 + }, "gateway_config.network_config.ip": { "description": "The gateway server ip.", "privacy": "Public", diff --git a/crates/gateway/src/compilation.rs b/crates/gateway/src/compilation.rs index 30da8d97f..3017241dd 100644 --- a/crates/gateway/src/compilation.rs +++ b/crates/gateway/src/compilation.rs @@ -20,9 +20,9 @@ use crate::utils::is_subsequence; #[path = "compilation_test.rs"] mod compilation_test; +// TODO(Define a function for `compile_contract_class` - which ignores the `config` parameter). #[derive(Clone)] pub struct GatewayCompiler { - #[allow(dead_code)] pub config: GatewayCompilerConfig, } @@ -41,7 +41,7 @@ impl GatewayCompiler { let casm_contract_class = self.compile(cairo_lang_contract_class)?; validate_compiled_class_hash(&casm_contract_class, tx.compiled_class_hash)?; - self.validate_casm_class(&casm_contract_class)?; + self.validate_casm(&casm_contract_class)?; build_result_class_info( casm_contract_class, @@ -66,6 +66,12 @@ impl GatewayCompiler { } } + fn validate_casm(&self, casm_contract_class: &CasmContractClass) -> Result<(), GatewayError> { + self.validate_casm_class(casm_contract_class)?; + self.validate_casm_class_size(casm_contract_class)?; + Ok(()) + } + // TODO(Arni): Add test. fn validate_casm_class(&self, contract_class: &CasmContractClass) -> Result<(), GatewayError> { let CasmContractEntryPoints { external, l1_handler, constructor } = @@ -84,6 +90,30 @@ impl GatewayCompiler { } Ok(()) } + + fn validate_casm_class_size( + &self, + casm_contract_class: &CasmContractClass, + ) -> Result<(), GatewayError> { + let bytecode_size = casm_contract_class.bytecode.len(); + if bytecode_size > self.config.max_bytecode_size { + return Err(GatewayError::CasmBytecodeSizeTooLarge { + bytecode_size, + max_bytecode_size: self.config.max_bytecode_size, + }); + } + let contract_class_object_size = serde_json::to_string(&casm_contract_class) + .expect("Unexpected error serializing Casm contract class.") + .len(); + if contract_class_object_size > self.config.max_raw_class_size { + return Err(GatewayError::CasmContractClassObjectSizeTooLarge { + contract_class_object_size, + max_contract_class_object_size: self.config.max_raw_class_size, + }); + } + + Ok(()) + } } // TODO(Arni): Add to a config. diff --git a/crates/gateway/src/compilation_test.rs b/crates/gateway/src/compilation_test.rs index fc1a0cef3..d4d2dc33b 100644 --- a/crates/gateway/src/compilation_test.rs +++ b/crates/gateway/src/compilation_test.rs @@ -8,6 +8,7 @@ use starknet_api::rpc_transaction::{RPCDeclareTransaction, RPCTransaction}; use starknet_sierra_compile::errors::CompilationUtilError; use crate::compilation::GatewayCompiler; +use crate::config::GatewayCompilerConfig; use crate::errors::GatewayError; #[fixture] @@ -35,6 +36,50 @@ fn test_compile_contract_class_compiled_class_hash_missmatch(gateway_compiler: G ); } +#[rstest] +#[case::bytecode_size( + GatewayCompilerConfig { max_bytecode_size: 1, max_raw_class_size: usize::MAX}, + GatewayError::CasmBytecodeSizeTooLarge { bytecode_size: 4800, max_bytecode_size: 1 } +)] +#[case::raw_class_size( + GatewayCompilerConfig { max_bytecode_size: usize::MAX, max_raw_class_size: 1}, + GatewayError::CasmContractClassObjectSizeTooLarge { + contract_class_object_size: 111037, max_contract_class_object_size: 1 + } +)] +fn test_compile_contract_class_size_validation( + #[case] sierra_to_casm_compilation_config: GatewayCompilerConfig, + #[case] expected_error: GatewayError, +) { + let declare_tx = match declare_tx() { + RPCTransaction::Declare(declare_tx) => declare_tx, + _ => panic!("Invalid transaction type"), + }; + + let gateway_compiler = GatewayCompiler { config: sierra_to_casm_compilation_config }; + let result = gateway_compiler.handle_declare_tx(&declare_tx); + if let GatewayError::CasmBytecodeSizeTooLarge { + bytecode_size: expected_bytecode_size, .. + } = expected_error + { + assert_matches!( + result.unwrap_err(), + GatewayError::CasmBytecodeSizeTooLarge { bytecode_size, .. } + if bytecode_size == expected_bytecode_size + ) + } else if let GatewayError::CasmContractClassObjectSizeTooLarge { + contract_class_object_size: expected_contract_class_object_size, + .. + } = expected_error + { + assert_matches!( + result.unwrap_err(), + GatewayError::CasmContractClassObjectSizeTooLarge { contract_class_object_size, .. } + if contract_class_object_size == expected_contract_class_object_size + ) + } +} + #[rstest] fn test_compile_contract_class_bad_sierra(gateway_compiler: GatewayCompiler) { let mut tx = assert_matches!( diff --git a/crates/gateway/src/config.rs b/crates/gateway/src/config.rs index 49970fa7a..8c459e2b8 100644 --- a/crates/gateway/src/config.rs +++ b/crates/gateway/src/config.rs @@ -11,6 +11,9 @@ use validator::Validate; use crate::compiler_version::VersionId; +const MAX_BYTECODE_SIZE: usize = 81920; +const MAX_RAW_CLASS_SIZE: usize = 4089446; + #[derive(Clone, Debug, Default, Serialize, Deserialize, Validate, PartialEq)] pub struct GatewayConfig { pub network_config: GatewayNetworkConfig, @@ -88,8 +91,8 @@ impl Default for StatelessTransactionValidatorConfig { validate_non_zero_l2_gas_fee: false, max_calldata_length: 4000, max_signature_length: 4000, - max_bytecode_size: 81920, - max_raw_class_size: 4089446, + max_bytecode_size: MAX_BYTECODE_SIZE, + max_raw_class_size: MAX_RAW_CLASS_SIZE, min_sierra_version: VersionId { major: 1, minor: 1, patch: 0 }, max_sierra_version: VersionId { major: 1, minor: 5, patch: usize::MAX }, } @@ -295,12 +298,33 @@ impl StatefulTransactionValidatorConfig { } } } +#[derive(Clone, Copy, Debug, Serialize, Deserialize, Validate, PartialEq)] +pub struct GatewayCompilerConfig { + pub max_bytecode_size: usize, + pub max_raw_class_size: usize, +} -#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, Validate, PartialEq)] -pub struct GatewayCompilerConfig {} +impl Default for GatewayCompilerConfig { + fn default() -> Self { + Self { max_bytecode_size: MAX_BYTECODE_SIZE, max_raw_class_size: MAX_RAW_CLASS_SIZE } + } +} impl SerializeConfig for GatewayCompilerConfig { fn dump(&self) -> BTreeMap { - BTreeMap::new() + BTreeMap::from_iter([ + ser_param( + "max_bytecode_size", + &self.max_bytecode_size, + "Limitation of contract bytecode size", + ParamPrivacyInput::Public, + ), + ser_param( + "max_raw_class_size", + &self.max_raw_class_size, + "Limitation of contract class object size", + ParamPrivacyInput::Public, + ), + ]) } } diff --git a/crates/gateway/src/errors.rs b/crates/gateway/src/errors.rs index 24d5ef74b..9517a969e 100644 --- a/crates/gateway/src/errors.rs +++ b/crates/gateway/src/errors.rs @@ -19,6 +19,19 @@ use crate::compiler_version::{VersionId, VersionIdError}; /// Errors directed towards the end-user, as a result of gateway requests. #[derive(Debug, Error)] pub enum GatewayError { + #[error( + "Cannot declare Casm contract class with bytecode size of {bytecode_size}; max allowed \ + size: {max_bytecode_size}." + )] + CasmBytecodeSizeTooLarge { bytecode_size: usize, max_bytecode_size: usize }, + #[error( + "Cannot declare Casm contract class with size of {contract_class_object_size}; max \ + allowed size: {max_contract_class_object_size}." + )] + CasmContractClassObjectSizeTooLarge { + contract_class_object_size: usize, + max_contract_class_object_size: usize, + }, #[error(transparent)] CompilationError(#[from] CompilationUtilError), #[error( diff --git a/crates/gateway/src/gateway_test.rs b/crates/gateway/src/gateway_test.rs index c443db83c..d96370338 100644 --- a/crates/gateway/src/gateway_test.rs +++ b/crates/gateway/src/gateway_test.rs @@ -40,21 +40,28 @@ pub fn app_state( mempool_client: SharedMempoolClient, state_reader_factory: TestStateReaderFactory, ) -> AppState { + const MAX_BYTECODE_SIZE: usize = 10000; + const MAX_RAW_CLASS_SIZE: usize = 1000000; AppState { stateless_tx_validator: StatelessTransactionValidator { config: StatelessTransactionValidatorConfig { validate_non_zero_l1_gas_fee: true, max_calldata_length: 10, max_signature_length: 2, - max_bytecode_size: 10000, - max_raw_class_size: 1000000, + max_bytecode_size: MAX_BYTECODE_SIZE, + max_raw_class_size: MAX_RAW_CLASS_SIZE, ..Default::default() }, }, stateful_tx_validator: Arc::new(StatefulTransactionValidator { config: StatefulTransactionValidatorConfig::create_for_testing(), }), - gateway_compiler: GatewayCompiler { config: GatewayCompilerConfig {} }, + gateway_compiler: GatewayCompiler { + config: GatewayCompilerConfig { + max_bytecode_size: MAX_BYTECODE_SIZE, + max_raw_class_size: MAX_RAW_CLASS_SIZE, + }, + }, state_reader_factory: Arc::new(state_reader_factory), mempool_client, } diff --git a/crates/gateway/src/stateful_transaction_validator_test.rs b/crates/gateway/src/stateful_transaction_validator_test.rs index 5fd889272..dc3cbd83a 100644 --- a/crates/gateway/src/stateful_transaction_validator_test.rs +++ b/crates/gateway/src/stateful_transaction_validator_test.rs @@ -82,7 +82,7 @@ fn test_stateful_tx_validator( }; let optional_class_info = match &external_tx { RPCTransaction::Declare(declare_tx) => Some( - GatewayCompiler { config: GatewayCompilerConfig {} } + GatewayCompiler { config: GatewayCompilerConfig::default() } .handle_declare_tx(declare_tx) .unwrap(), ),