diff --git a/Cargo.lock b/Cargo.lock index 94db88b7..7281470d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5234,6 +5234,7 @@ dependencies = [ "cairo-lang-starknet-classes", "cairo-vm", "hyper", + "lazy_static", "papyrus_config", "papyrus_rpc", "pretty_assertions", diff --git a/crates/gateway/Cargo.toml b/crates/gateway/Cargo.toml index facf3100..707c7e7a 100644 --- a/crates/gateway/Cargo.toml +++ b/crates/gateway/Cargo.toml @@ -18,6 +18,7 @@ blockifier= { workspace = true , features = ["testing"] } cairo-lang-starknet-classes.workspace = true cairo-vm.workspace = true hyper.workspace = true +lazy_static.workspace = true papyrus_config.workspace = true papyrus_rpc.workspace = true reqwest.workspace = true diff --git a/crates/gateway/src/errors.rs b/crates/gateway/src/errors.rs index a1cc259e..ac43ed59 100644 --- a/crates/gateway/src/errors.rs +++ b/crates/gateway/src/errors.rs @@ -23,7 +23,7 @@ pub enum GatewayError { CompilationError(#[from] CompilationUtilError), #[error( "The supplied compiled class hash {supplied:?} does not match the hash of the Casm class \ - compiled from the supplied Sierra {hash_result:?}." + compiled from the supplied Sierra {hash_result:?}" )] CompiledClassHashMismatch { supplied: CompiledClassHash, hash_result: CompiledClassHash }, #[error(transparent)] @@ -38,6 +38,8 @@ pub enum GatewayError { StatefulTransactionValidatorError(#[from] StatefulTransactionValidatorError), #[error(transparent)] StatelessTransactionValidatorError(#[from] StatelessTransactionValidatorError), + #[error("{builtins:?} is not a subsquence of {supported_builtins:?}")] + UnsupportedBuiltins { builtins: Vec, supported_builtins: Vec }, } impl IntoResponse for GatewayError { diff --git a/crates/gateway/src/gateway.rs b/crates/gateway/src/gateway.rs index f392eb63..a9bc173f 100644 --- a/crates/gateway/src/gateway.rs +++ b/crates/gateway/src/gateway.rs @@ -9,6 +9,10 @@ use axum::routing::{get, post}; use axum::{Json, Router}; use blockifier::execution::contract_class::{ClassInfo, ContractClass, ContractClassV1}; use blockifier::execution::execution_utils::felt_to_stark_felt; +use cairo_lang_starknet_classes::casm_contract_class::{ + CasmContractClass, CasmContractEntryPoints, +}; +use lazy_static::lazy_static; use starknet_api::core::CompiledClassHash; use starknet_api::rpc_transaction::{RPCDeclareTransaction, RPCTransaction}; use starknet_api::transaction::TransactionHash; @@ -25,7 +29,7 @@ use crate::rpc_state_reader::RpcStateReaderFactory; use crate::state_reader::StateReaderFactory; use crate::stateful_transaction_validator::StatefulTransactionValidator; use crate::stateless_transaction_validator::StatelessTransactionValidator; -use crate::utils::{external_tx_to_thin_tx, get_sender_address}; +use crate::utils::{external_tx_to_thin_tx, get_sender_address, is_subsequence}; #[cfg(test)] #[path = "gateway_test.rs"] @@ -161,6 +165,7 @@ pub fn compile_contract_class(declare_tx: &RPCDeclareTransaction) -> GatewayResu return Err(GatewayError::CompilationError(CompilationUtilError::CompilationPanic)); } }; + validate_casm_class(&casm_contract_class)?; let hash_result = CompiledClassHash(felt_to_stark_felt(&casm_contract_class.compiled_class_hash())); @@ -199,3 +204,33 @@ impl ComponentRunner for Gateway { Ok(()) } } + +// TODO(Arni): Add to a config. +// TODO(Arni): Use the Builtin enum from Starknet-api, and explicitly tag each builtin as supported +// or unsupported so that the compiler would alert us on new builtins. +lazy_static! { + static ref SUPPORTED_BUILTINS: Vec = { + // The OS expects this order for the builtins. + const SUPPORTED_BUILTIN_NAMES: [&str; 7] = + ["pedersen", "range_check", "ecdsa", "bitwise", "ec_op", "poseidon", "segment_arena"]; + SUPPORTED_BUILTIN_NAMES.iter().map(|builtin| builtin.to_string()).collect::>() + }; +} + +// TODO(Arni): Add test. +fn validate_casm_class(contract_class: &CasmContractClass) -> Result<(), GatewayError> { + let CasmContractEntryPoints { external, l1_handler, constructor } = + &contract_class.entry_points_by_type; + let entry_points_iterator = external.iter().chain(l1_handler.iter()).chain(constructor.iter()); + + for entry_point in entry_points_iterator { + let builtins = &entry_point.builtins; + if !is_subsequence(builtins, &SUPPORTED_BUILTINS) { + return Err(GatewayError::UnsupportedBuiltins { + builtins: builtins.clone(), + supported_builtins: SUPPORTED_BUILTINS.to_vec(), + }); + } + } + Ok(()) +} diff --git a/crates/gateway/src/utils.rs b/crates/gateway/src/utils.rs index f2a37f1c..6cebd08d 100644 --- a/crates/gateway/src/utils.rs +++ b/crates/gateway/src/utils.rs @@ -17,6 +17,10 @@ use starknet_mempool_types::mempool_types::ThinTransaction; use crate::errors::StatefulTransactionValidatorResult; +#[cfg(test)] +#[path = "utils_test.rs"] +mod utils_test; + macro_rules! implement_ref_getters { ($(($member_name:ident, $member_type:ty));* $(;)?) => { $(fn $member_name(&self) -> &$member_type { @@ -155,3 +159,20 @@ pub fn get_tx_hash(tx: &AccountTransaction) -> TransactionHash { AccountTransaction::Invoke(tx) => tx.tx_hash, } } + +/// Checks whether 'subsequence' is a subsequence of 'sequence'. +pub fn is_subsequence(subsequence: &[T], sequence: &[T]) -> bool { + let mut offset = 0; + + for item in sequence { + if offset == subsequence.len() { + return true; + } + + if item == &subsequence[offset] { + offset += 1; + } + } + + offset == subsequence.len() +} diff --git a/crates/gateway/src/utils_test.rs b/crates/gateway/src/utils_test.rs new file mode 100644 index 00000000..7b9d2fdb --- /dev/null +++ b/crates/gateway/src/utils_test.rs @@ -0,0 +1,78 @@ +use pretty_assertions::assert_eq; +use rstest::rstest; + +use crate::utils::is_subsequence; + +#[rstest] +#[case::empty( + &[], + &[], + true +)] +#[case::empty_subsequence( + &[], + &["a", "b"], + true +)] +#[case::empty_sequence( + &["a"], + &[], + false +)] +#[case::subsequence_1( + &["a"], + &["a", "b", "c"], + true +)] +#[case::subsequence_2( + &["b"], + &["a", "b", "c"], + true +)] +#[case::subsequence_3( + &["c"], + &["a", "b", "c"], + true +)] +#[case::subsequence_4( + &["a", "b"], + &["a", "b", "c"], + true +)] +#[case::subsequence_5( + &["a", "c"], + &["a", "b", "c"], + true +)] +#[case::subsequence_6( + &["b", "c"], + &["a", "b", "c"], + true +)] +#[case::subsequence_7( + &["a", "b", "c"], + &["a", "b", "c"], + true +)] +#[case::out_of_order_1( + &["b", "a"], + &["a", "b", "c"], + false +)] +#[case::out_of_order_2( + &["b", "a", "c"], + &["a", "b", "c"], + false +)] +#[case::unrelated( + &["a", "b", "d"], + &["a", "b", "c"], + false +)] +fn test_is_subsequence( + #[case] subsequence: &[&str], + #[case] sequence: &[&str], + #[case] expected_result: bool, +) { + assert_eq!(is_subsequence(subsequence, sequence), expected_result); +}