From 96e05b2730a5461cb20d8dedf1804c064bb48737 Mon Sep 17 00:00:00 2001 From: Arni Hod Date: Wed, 5 Jun 2024 16:14:07 +0300 Subject: [PATCH] feat: add declare specific size related stateless tx validator --- crates/gateway/src/config.rs | 5 +- crates/gateway/src/errors.rs | 13 ++++ crates/gateway/src/starknet_api_test_utils.rs | 8 ++- .../src/stateless_transaction_validator.rs | 44 +++++++++++-- .../stateless_transaction_validator_test.rs | 64 +++++++++++++++++-- 5 files changed, 121 insertions(+), 13 deletions(-) diff --git a/crates/gateway/src/config.rs b/crates/gateway/src/config.rs index 431042dd6..05a065794 100644 --- a/crates/gateway/src/config.rs +++ b/crates/gateway/src/config.rs @@ -66,9 +66,12 @@ pub struct StatelessTransactionValidatorConfig { // If true, validates that the resource bounds are not zero. pub validate_non_zero_l1_gas_fee: bool, pub validate_non_zero_l2_gas_fee: bool, - pub max_calldata_length: usize, pub max_signature_length: usize, + + // Declare txs specific config. + 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 3483775ca..996b91791 100644 --- a/crates/gateway/src/errors.rs +++ b/crates/gateway/src/errors.rs @@ -46,6 +46,19 @@ pub enum StatelessTransactionValidatorError { (allowed length: {max_signature_length})." )] SignatureTooLong { signature_length: usize, max_signature_length: usize }, + #[error( + "Cannot declare contract class with bytecode size of {bytecode_size}; max allowed size: \ + {max_bytecode_size}." + )] + BytecodeSizeTooLarge { bytecode_size: usize, max_bytecode_size: usize }, + #[error( + "Cannot declare contract class with size of {contract_class_object_size}; max allowed \ + size: {max_contract_class_object_size}." + )] + ContractClassObjectSizeTooLarge { + 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 d6db26f17..b475cc99f 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 28179fb71..94d08ee92 100644 --- a/crates/gateway/src/stateless_transaction_validator.rs +++ b/crates/gateway/src/stateless_transaction_validator.rs @@ -1,6 +1,6 @@ use starknet_api::external_transaction::{ - ExternalDeployAccountTransaction, ExternalInvokeTransaction, ExternalTransaction, - ResourceBoundsMapping, + ExternalDeclareTransaction, ExternalDeployAccountTransaction, ExternalInvokeTransaction, + ExternalTransaction, ResourceBoundsMapping, }; use starknet_api::transaction::Resource; @@ -24,6 +24,9 @@ impl StatelessTransactionValidator { self.validate_resource_bounds(tx)?; self.validate_tx_size(tx)?; + if let ExternalTransaction::Declare(declare_tx) = tx { + self.validate_declare_tx(declare_tx)?; + } Ok(()) } @@ -95,9 +98,42 @@ impl StatelessTransactionValidator { Ok(()) } -} -// Utilities. + fn validate_declare_tx( + &self, + declare_tx: &ExternalDeclareTransaction, + ) -> StatelessTransactionValidatorResult<()> { + let contract_class = match declare_tx { + ExternalDeclareTransaction::V3(tx) => &tx.contract_class, + }; + self.validate_class_length(contract_class) + } + + fn validate_class_length( + &self, + contract_class: &starknet_api::external_transaction::ContractClass, + ) -> StatelessTransactionValidatorResult<()> { + let bytecode_size = contract_class.sierra_program.len(); + if bytecode_size > self.config.max_bytecode_size { + return Err(StatelessTransactionValidatorError::BytecodeSizeTooLarge { + bytecode_size, + max_bytecode_size: self.config.max_bytecode_size, + }); + } + + let contract_class_object_size = serde_json::to_string(&contract_class) + .expect("Unexpected error serializing contract class.") + .len(); + if contract_class_object_size > self.config.max_raw_class_size { + return Err(StatelessTransactionValidatorError::ContractClassObjectSizeTooLarge { + contract_class_object_size, + max_contract_class_object_size: self.config.max_raw_class_size, + }); + } + + Ok(()) + } +} fn validate_resource_is_non_zero( resource_bounds_mapping: &ResourceBoundsMapping, diff --git a/crates/gateway/src/stateless_transaction_validator_test.rs b/crates/gateway/src/stateless_transaction_validator_test.rs index 60e011b97..8c7500cd7 100644 --- a/crates/gateway/src/stateless_transaction_validator_test.rs +++ b/crates/gateway/src/stateless_transaction_validator_test.rs @@ -1,17 +1,18 @@ 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::starknet_api_test_utils::{ - create_resource_bounds_mapping, external_tx_for_testing, zero_resource_bounds_mapping, - TransactionType, NON_EMPTY_RESOURCE_BOUNDS, + create_resource_bounds_mapping, external_declare_tx, external_tx_for_testing, + 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 = @@ -20,6 +21,8 @@ const DEFAULT_VALIDATOR_CONFIG_FOR_TESTING: StatelessTransactionValidatorConfig validate_non_zero_l2_gas_fee: false, max_calldata_length: 1, max_signature_length: 1, + max_bytecode_size: 10000, + max_raw_class_size: 100000, }; #[rstest] @@ -180,3 +183,52 @@ 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_length = config_max_bytecode_size + 1; + let sierra_program = vec![stark_felt!(1_u128); sierra_program_length]; + 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::BytecodeSizeTooLarge { + bytecode_size, + max_bytecode_size + } if ( + bytecode_size, max_bytecode_size + ) == (sierra_program_length, 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_length = 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::ContractClassObjectSizeTooLarge { + contract_class_object_size, max_contract_class_object_size + } if ( + contract_class_object_size, max_contract_class_object_size + ) == (contract_class_length, config_max_raw_class_size) + ) +}