diff --git a/crates/gateway/src/config.rs b/crates/gateway/src/config.rs index 431042dd..bfbdf79a 100644 --- a/crates/gateway/src/config.rs +++ b/crates/gateway/src/config.rs @@ -69,6 +69,9 @@ pub struct StatelessTransactionValidatorConfig { pub max_calldata_length: usize, pub max_signature_length: usize, + + pub max_bytecode_size: usize, + pub max_raw_class_size: usize, } impl SerializeConfig for StatelessTransactionValidatorConfig { diff --git a/crates/gateway/src/errors.rs b/crates/gateway/src/errors.rs index 3483775c..b89e9eb4 100644 --- a/crates/gateway/src/errors.rs +++ b/crates/gateway/src/errors.rs @@ -46,6 +46,34 @@ pub enum StatelessTransactionValidatorError { (allowed length: {max_signature_length})." )] SignatureTooLong { signature_length: usize, max_signature_length: usize }, + #[error(transparent)] + DeclareTransactionError(#[from] DeclareTransactionError), + // TODO(Arni): Cosider a transparant error for serde_json::Error. + #[error("Error serializing object: {object_name}")] + SerializationError { object_name: String }, +} + +#[derive(Debug, Error)] +#[cfg_attr(test, derive(PartialEq))] +pub enum DeclareTransactionError { + #[error( + "Declared contract class {bytecode_language} bytecode size is {bytecode_size}. It must be \ + less then {max_bytecode_size}." + )] + BytecodeSizeTooLarge { + bytecode_language: String, + bytecode_size: usize, + max_bytecode_size: usize, + }, + #[error( + "Declared contract class {bytecode_language} size is {contract_class_object_size}. It \ + must be less then {max_contract_class_object_size}." + )] + ContractClassObjectSizeTooLarge { + bytecode_language: String, + contract_class_object_size: usize, + max_contract_class_object_size: usize, + }, } pub type StatelessTransactionValidatorResult = Result; diff --git a/crates/gateway/src/starknet_api_test_utils.rs b/crates/gateway/src/starknet_api_test_utils.rs index 79125301..5f95648f 100644 --- a/crates/gateway/src/starknet_api_test_utils.rs +++ b/crates/gateway/src/starknet_api_test_utils.rs @@ -1,7 +1,6 @@ use blockifier::test_utils::contracts::FeatureContract; use blockifier::test_utils::{create_trivial_calldata, CairoVersion, NonceManager}; use serde_json::to_string_pretty; -use starknet_api::calldata; use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce}; use starknet_api::data_availability::DataAvailabilityMode; use starknet_api::external_transaction::{ @@ -15,6 +14,7 @@ use starknet_api::transaction::{ AccountDeploymentData, Calldata, ContractAddressSalt, PaymasterData, ResourceBounds, Tip, TransactionSignature, TransactionVersion, }; +use starknet_api::{calldata, stark_felt}; use crate::{declare_tx_args, deploy_account_tx_args, invoke_tx_args}; @@ -47,7 +47,11 @@ pub fn external_tx_for_testing( ) -> ExternalTransaction { match tx_type { TransactionType::Declare => { - external_declare_tx(declare_tx_args!(resource_bounds, signature)) + let contract_class = ContractClass { + sierra_program: vec![stark_felt!(1_u32); 3], + ..ContractClass::default() + }; + external_declare_tx(declare_tx_args!(resource_bounds, signature, contract_class)) } TransactionType::DeployAccount => external_deploy_account_tx( deploy_account_tx_args!(resource_bounds, constructor_calldata: calldata, signature), diff --git a/crates/gateway/src/stateless_transaction_validator.rs b/crates/gateway/src/stateless_transaction_validator.rs index 28179fb7..72f35341 100644 --- a/crates/gateway/src/stateless_transaction_validator.rs +++ b/crates/gateway/src/stateless_transaction_validator.rs @@ -1,11 +1,14 @@ use starknet_api::external_transaction::{ - ExternalDeployAccountTransaction, ExternalInvokeTransaction, ExternalTransaction, - ResourceBoundsMapping, + ExternalDeclareTransaction, ExternalDeclareTransactionV3, ExternalDeployAccountTransaction, + ExternalInvokeTransaction, ExternalTransaction, ResourceBoundsMapping, }; use starknet_api::transaction::Resource; use crate::config::StatelessTransactionValidatorConfig; -use crate::errors::{StatelessTransactionValidatorError, StatelessTransactionValidatorResult}; +use crate::errors::{ + DeclareTransactionError, StatelessTransactionValidatorError, + StatelessTransactionValidatorResult, +}; #[cfg(test)] #[path = "stateless_transaction_validator_test.rs"] @@ -24,6 +27,9 @@ impl StatelessTransactionValidator { self.validate_resource_bounds(tx)?; self.validate_tx_size(tx)?; + if let ExternalTransaction::Declare(ExternalDeclareTransaction::V3(declare_tx)) = tx { + self.validate_declare_tx(declare_tx)?; + } Ok(()) } @@ -95,10 +101,66 @@ impl StatelessTransactionValidator { Ok(()) } + + fn validate_declare_tx( + &self, + tx: &ExternalDeclareTransactionV3, + ) -> StatelessTransactionValidatorResult<()> { + self.validate_class_length(&tx.contract_class)?; + + Ok(()) + } + + fn validate_class_length( + &self, + contract_class: &starknet_api::external_transaction::ContractClass, + ) -> StatelessTransactionValidatorResult<()> { + let bytecode_size = contract_class.sierra_program.len(); + let serialized_class = serde_json::to_string(&contract_class).map_err(|_| { + StatelessTransactionValidatorError::SerializationError { + object_name: "contract class".to_string(), + } + })?; + let raw_class_size = serialized_class.len(); + + Ok(validate_class_size( + "Sierra".to_string(), + bytecode_size, + self.config.max_bytecode_size, + raw_class_size, + self.config.max_raw_class_size, + )?) + } } // Utilities. +fn validate_class_size( + bytecode_language: String, + bytecode_size: usize, + max_bytecode_size: usize, + raw_class_size: usize, + max_raw_class_size: usize, +) -> Result<(), DeclareTransactionError> { + if bytecode_size > max_bytecode_size { + return Err(DeclareTransactionError::BytecodeSizeTooLarge { + bytecode_language, + bytecode_size, + max_bytecode_size, + }); + } + + if raw_class_size > max_raw_class_size { + return Err(DeclareTransactionError::ContractClassObjectSizeTooLarge { + bytecode_language, + contract_class_object_size: raw_class_size, + max_contract_class_object_size: max_raw_class_size, + }); + } + + Ok(()) +} + fn validate_resource_is_non_zero( resource_bounds_mapping: &ResourceBoundsMapping, resource: Resource, diff --git a/crates/gateway/src/stateless_transaction_validator_test.rs b/crates/gateway/src/stateless_transaction_validator_test.rs index 8b650eaf..fc890b9a 100644 --- a/crates/gateway/src/stateless_transaction_validator_test.rs +++ b/crates/gateway/src/stateless_transaction_validator_test.rs @@ -1,17 +1,20 @@ use assert_matches::assert_matches; use rstest::rstest; -use starknet_api::calldata; -use starknet_api::external_transaction::ResourceBoundsMapping; +use starknet_api::external_transaction::{ContractClass, ResourceBoundsMapping}; use starknet_api::hash::StarkFelt; use starknet_api::transaction::{Calldata, Resource, ResourceBounds, TransactionSignature}; +use starknet_api::{calldata, stark_felt}; +use crate::config::StatelessTransactionValidatorConfig; +use crate::declare_tx_args; +use crate::errors::DeclareTransactionError; use crate::starknet_api_test_utils::{ - create_resource_bounds_mapping, external_tx_for_testing, non_zero_resource_bounds_mapping, - zero_resource_bounds_mapping, TransactionType, NON_EMPTY_RESOURCE_BOUNDS, + create_resource_bounds_mapping, external_declare_tx, external_tx_for_testing, + non_zero_resource_bounds_mapping, zero_resource_bounds_mapping, TransactionType, + NON_EMPTY_RESOURCE_BOUNDS, }; use crate::stateless_transaction_validator::{ - StatelessTransactionValidator, StatelessTransactionValidatorConfig, - StatelessTransactionValidatorError, + StatelessTransactionValidator, StatelessTransactionValidatorError, }; const DEFAULT_VALIDATOR_CONFIG_FOR_TESTING: StatelessTransactionValidatorConfig = @@ -21,6 +24,8 @@ const DEFAULT_VALIDATOR_CONFIG_FOR_TESTING: StatelessTransactionValidatorConfig max_calldata_length: 1, max_signature_length: 1, + max_bytecode_size: 10000, + max_raw_class_size: 100000, }; #[rstest] @@ -165,3 +170,59 @@ fn test_signature_too_long( } ); } + +#[test] +fn test_declare_bytecode_size_too_long() { + let config_max_bytecode_size = 10; + let tx_validator = StatelessTransactionValidator { + config: StatelessTransactionValidatorConfig { + max_bytecode_size: config_max_bytecode_size, + ..DEFAULT_VALIDATOR_CONFIG_FOR_TESTING + }, + }; + let sierra_program = vec![stark_felt!(1_u128); config_max_bytecode_size + 1]; + let sierra_program_length = sierra_program.len(); + let contract_class = ContractClass { sierra_program, ..Default::default() }; + let tx = external_declare_tx(declare_tx_args!(contract_class)); + + assert_matches!( + tx_validator.validate(&tx).unwrap_err(), + StatelessTransactionValidatorError::DeclareTransactionError( + DeclareTransactionError::BytecodeSizeTooLarge { + bytecode_language, + bytecode_size, + max_bytecode_size + } + ) => { + assert_eq!(bytecode_language, "Sierra"); + assert_eq!(bytecode_size, sierra_program_length); + assert_eq!(max_bytecode_size, config_max_bytecode_size); + } + ) +} + +#[test] +fn test_declare_contract_class_size_too_long() { + let config_max_raw_class_size = 100; + let tx_validator = StatelessTransactionValidator { + config: StatelessTransactionValidatorConfig { + max_raw_class_size: config_max_raw_class_size, + ..DEFAULT_VALIDATOR_CONFIG_FOR_TESTING + }, + }; + let contract_class = + ContractClass { sierra_program: vec![stark_felt!(1_u128); 3], ..Default::default() }; + let contract_class_len = serde_json::to_string(&contract_class).unwrap().len(); + let tx = external_declare_tx(declare_tx_args!(contract_class)); + + assert_matches!( + tx_validator.validate(&tx).unwrap_err(), + StatelessTransactionValidatorError::DeclareTransactionError( + DeclareTransactionError::ContractClassObjectSizeTooLarge { bytecode_language, contract_class_object_size, max_contract_class_object_size } + ) => { + assert_eq!(bytecode_language, "Sierra"); + assert_eq!(contract_class_object_size, contract_class_len); + assert_eq!(max_contract_class_object_size, config_max_raw_class_size); + } + ) +}