From 9109bbb3b6892103a6e6e36d14f114a1c9216322 Mon Sep 17 00:00:00 2001 From: Arni Hod Date: Tue, 2 Jul 2024 13:19:30 +0300 Subject: [PATCH] feat: declare post compilation builtin validation --- crates/gateway/Cargo.toml | 2 +- crates/gateway/src/errors.rs | 4 +- crates/gateway/src/gateway.rs | 34 +++++++++++++- crates/gateway/src/utils.rs | 21 +++++++++ crates/gateway/src/utils_test.rs | 78 ++++++++++++++++++++++++++++++++ 5 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 crates/gateway/src/utils_test.rs diff --git a/crates/gateway/Cargo.toml b/crates/gateway/Cargo.toml index f7ee96483..c64329b2e 100644 --- a/crates/gateway/Cargo.toml +++ b/crates/gateway/Cargo.toml @@ -34,4 +34,4 @@ validator.workspace = true assert_matches.workspace = true pretty_assertions.workspace = true rstest.workspace = true -starknet_mempool = { path = "../mempool", version = "0.0" } \ No newline at end of file +starknet_mempool = { path = "../mempool", version = "0.0" } diff --git a/crates/gateway/src/errors.rs b/crates/gateway/src/errors.rs index a1cc259e2..ac43ed592 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 527766bdb..606fc95cc 100644 --- a/crates/gateway/src/gateway.rs +++ b/crates/gateway/src/gateway.rs @@ -8,6 +8,9 @@ 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 starknet_api::core::CompiledClassHash; use starknet_api::rpc_transaction::{RPCDeclareTransaction, RPCTransaction}; use starknet_api::transaction::TransactionHash; @@ -23,7 +26,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"] @@ -159,6 +162,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())); @@ -188,3 +192,31 @@ pub fn create_gateway( let state_reader_factory = Arc::new(RpcStateReaderFactory { config: rpc_state_reader_config }); Gateway::new(config, state_reader_factory, client) } + +// 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. + +// The OS expects this order for the builtins. +const SUPPORTED_BUILTINS: [&str; 7] = + ["pedersen", "range_check", "ecdsa", "bitwise", "ec_op", "poseidon", "segment_arena"]; + +// 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()); + + let supported_builtins: Vec = + SUPPORTED_BUILTINS.iter().map(|builtin| builtin.to_string()).collect(); + 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, + }); + } + } + Ok(()) +} diff --git a/crates/gateway/src/utils.rs b/crates/gateway/src/utils.rs index f2a37f1ca..6cebd08d4 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 000000000..7b9d2fdb9 --- /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); +}