diff --git a/crates/gateway/src/errors.rs b/crates/gateway/src/errors.rs index 24d5ef74..75c30c0d 100644 --- a/crates/gateway/src/errors.rs +++ b/crates/gateway/src/errors.rs @@ -88,6 +88,8 @@ pub enum StatelessTransactionValidatorError { contract_class_object_size: usize, max_contract_class_object_size: usize, }, + #[error("Entry points must be unique and sorted.")] + EntryPointsNotUniquelySorted, } pub type StatelessTransactionValidatorResult = Result; diff --git a/crates/gateway/src/stateless_transaction_validator.rs b/crates/gateway/src/stateless_transaction_validator.rs index c66282c0..70805ab5 100644 --- a/crates/gateway/src/stateless_transaction_validator.rs +++ b/crates/gateway/src/stateless_transaction_validator.rs @@ -2,6 +2,7 @@ use starknet_api::rpc_transaction::{ RPCDeclareTransaction, RPCDeployAccountTransaction, RPCInvokeTransaction, RPCTransaction, ResourceBoundsMapping, }; +use starknet_api::state::EntryPoint; use starknet_api::transaction::Resource; use starknet_types_core::felt::Felt; @@ -106,7 +107,9 @@ impl StatelessTransactionValidator { RPCDeclareTransaction::V3(tx) => &tx.contract_class, }; self.validate_sierra_version(&contract_class.sierra_program)?; - self.validate_class_length(contract_class) + self.validate_class_length(contract_class)?; + self.validate_entry_points_sorted_and_unique(contract_class)?; + Ok(()) } fn validate_sierra_version( @@ -154,6 +157,24 @@ impl StatelessTransactionValidator { Ok(()) } + + fn validate_entry_points_sorted_and_unique( + &self, + contract_class: &starknet_api::rpc_transaction::ContractClass, + ) -> StatelessTransactionValidatorResult<()> { + let is_sorted_unique = |entry_points: &[EntryPoint]| { + entry_points.windows(2).all(|pair| pair[0].selector < pair[1].selector) + }; + + if is_sorted_unique(&contract_class.entry_points_by_type.constructor) + && is_sorted_unique(&contract_class.entry_points_by_type.external) + && is_sorted_unique(&contract_class.entry_points_by_type.l1handler) + { + return Ok(()); + } + + Err(StatelessTransactionValidatorError::EntryPointsNotUniquelySorted) + } } fn validate_resource_is_non_zero( diff --git a/crates/gateway/src/stateless_transaction_validator_test.rs b/crates/gateway/src/stateless_transaction_validator_test.rs index dd2109c9..0d14f632 100644 --- a/crates/gateway/src/stateless_transaction_validator_test.rs +++ b/crates/gateway/src/stateless_transaction_validator_test.rs @@ -1,3 +1,5 @@ +use std::vec; + use assert_matches::assert_matches; use mempool_test_utils::declare_tx_args; use mempool_test_utils::starknet_api_test_utils::{ @@ -5,13 +7,16 @@ use mempool_test_utils::starknet_api_test_utils::{ zero_resource_bounds_mapping, TransactionType, NON_EMPTY_RESOURCE_BOUNDS, }; use rstest::rstest; -use starknet_api::rpc_transaction::{ContractClass, ResourceBoundsMapping}; +use starknet_api::core::EntryPointSelector; +use starknet_api::rpc_transaction::{ContractClass, EntryPointByType, ResourceBoundsMapping}; +use starknet_api::state::EntryPoint; use starknet_api::transaction::{Calldata, Resource, ResourceBounds, TransactionSignature}; use starknet_api::{calldata, felt}; use starknet_types_core::felt::Felt; use crate::compiler_version::{VersionId, VersionIdError}; use crate::config::StatelessTransactionValidatorConfig; +use crate::errors::StatelessTransactionValidatorResult; use crate::stateless_transaction_validator::{ StatelessTransactionValidator, StatelessTransactionValidatorError, }; @@ -326,3 +331,90 @@ fn test_declare_contract_class_size_too_long() { ) == (contract_class_length, config_max_raw_class_size) ) } + +#[rstest] +#[case::valid( + vec![ + EntryPoint { selector: EntryPointSelector(felt!(1_u128)), ..Default::default() }, + EntryPoint { selector: EntryPointSelector(felt!(2_u128)), ..Default::default() } + ], + Ok(()) +)] +#[case::no_entry_points( + vec![], + Ok(()) +)] +#[case::single_entry_point( + vec![ + EntryPoint { selector: EntryPointSelector(felt!(1_u128)), ..Default::default() } + ], + Ok(()) +)] +#[case::not_sorted( + vec![ + EntryPoint { selector: EntryPointSelector(felt!(2_u128)), ..Default::default() }, + EntryPoint { selector: EntryPointSelector(felt!(1_u128)), ..Default::default() }, + ], + Err(StatelessTransactionValidatorError::EntryPointsNotUniquelySorted) +)] +#[case::not_unique( + vec![ + EntryPoint { selector: EntryPointSelector(felt!(1_u128)), ..Default::default() }, + EntryPoint { selector: EntryPointSelector(felt!(1_u128)), ..Default::default() }, + ], + Err(StatelessTransactionValidatorError::EntryPointsNotUniquelySorted) +)] +#[case::many_entry_points( + vec![ + EntryPoint { selector: EntryPointSelector(felt!(1_u128)), ..Default::default() }, + EntryPoint { selector: EntryPointSelector(felt!(2_u128)), ..Default::default() }, + EntryPoint { selector: EntryPointSelector(felt!(1_u128)), ..Default::default() }, + ], + Err(StatelessTransactionValidatorError::EntryPointsNotUniquelySorted) +)] +fn test_declare_entry_points_not_sorted_by_selector( + #[case] entry_points: Vec, + #[case] expected: StatelessTransactionValidatorResult<()>, +) { + let tx_validator = + StatelessTransactionValidator { config: DEFAULT_VALIDATOR_CONFIG_FOR_TESTING }; + + let contract_class = ContractClass { + sierra_program: vec![felt!(1_u128); 3], + entry_points_by_type: EntryPointByType { + constructor: entry_points.clone(), + external: vec![], + l1handler: vec![], + }, + ..Default::default() + }; + let tx = external_declare_tx(declare_tx_args!(contract_class)); + + assert_eq!(tx_validator.validate(&tx), expected); + + let contract_class = ContractClass { + sierra_program: vec![felt!(1_u128); 3], + entry_points_by_type: EntryPointByType { + constructor: vec![], + external: entry_points.clone(), + l1handler: vec![], + }, + ..Default::default() + }; + let tx = external_declare_tx(declare_tx_args!(contract_class)); + + assert_eq!(tx_validator.validate(&tx), expected); + + let contract_class = ContractClass { + sierra_program: vec![felt!(1_u128); 3], + entry_points_by_type: EntryPointByType { + constructor: vec![], + external: vec![], + l1handler: entry_points, + }, + ..Default::default() + }; + let tx = external_declare_tx(declare_tx_args!(contract_class)); + + assert_eq!(tx_validator.validate(&tx), expected); +}