From c2d41e8e5554183d9c4a7b7ac4c8c7aa6522b655 Mon Sep 17 00:00:00 2001 From: Arni Hod Date: Wed, 26 Jun 2024 14:39:07 +0300 Subject: [PATCH] feat: declare post compilation builtin validation --- crates/gateway/src/errors.rs | 4 +- crates/gateway/src/gateway.rs | 32 +++++++++++++++- crates/gateway/src/utils.rs | 15 ++++++++ crates/gateway/src/utils_test.rs | 66 ++++++++++++++++++++++++++++++++ 4 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 crates/gateway/src/utils_test.rs diff --git a/crates/gateway/src/errors.rs b/crates/gateway/src/errors.rs index 3b65f783d..d3effbbc0 100644 --- a/crates/gateway/src/errors.rs +++ b/crates/gateway/src/errors.rs @@ -22,7 +22,7 @@ pub enum GatewayError { CompilationError(#[from] starknet_sierra_compile::compile::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)] @@ -37,6 +37,8 @@ pub enum GatewayError { StatefulTransactionValidatorError(#[from] StatefulTransactionValidatorError), #[error(transparent)] StatelessTransactionValidatorError(#[from] StatelessTransactionValidatorError), + #[error("{builtins:?} is not a subsquence of {supported_builtins:?}")] + SupportedBuiltins { builtins: Vec, supported_builtins: Vec }, } impl IntoResponse for GatewayError { diff --git a/crates/gateway/src/gateway.rs b/crates/gateway/src/gateway.rs index 12464589d..b8afb53cf 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; @@ -22,7 +25,7 @@ use crate::starknet_api_test_utils::get_sender_address; 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; +use crate::utils::{external_tx_to_thin_tx, is_subsequence}; #[cfg(test)] #[path = "gateway_test.rs"] @@ -158,6 +161,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())); @@ -178,3 +182,29 @@ pub fn compile_contract_class(declare_tx: &RPCDeclareTransaction) -> GatewayResu )?; Ok(class_info) } + +// TODO(Arni): Add to a config. +fn get_supported_builtins() -> Vec { + let builtins = + ["pedersen", "range_check", "ecdsa", "bitwise", "ec_op", "poseidon", "segment_arena"]; + builtins.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()); + + let supported_builtins = &get_supported_builtins(); + for entry_point in entry_points_iterator { + let builtins = &entry_point.builtins; + if !is_subsequence(builtins, supported_builtins) { + return Err(GatewayError::SupportedBuiltins { + builtins: builtins.clone(), + supported_builtins: supported_builtins.clone(), + }); + } + } + Ok(()) +} diff --git a/crates/gateway/src/utils.rs b/crates/gateway/src/utils.rs index 2e438f63e..b371f57a4 100644 --- a/crates/gateway/src/utils.rs +++ b/crates/gateway/src/utils.rs @@ -18,6 +18,10 @@ use starknet_mempool_types::mempool_types::ThinTransaction; use crate::errors::StatefulTransactionValidatorResult; use crate::starknet_api_test_utils::get_sender_address; +#[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 { @@ -145,3 +149,14 @@ 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: &[String], sequence: &[String]) -> bool { + let mut seq_iter = sequence.iter(); + for item in subsequence { + if !seq_iter.any(|x| x == item) { + return false; + } + } + true +} diff --git a/crates/gateway/src/utils_test.rs b/crates/gateway/src/utils_test.rs new file mode 100644 index 000000000..780a3cde8 --- /dev/null +++ b/crates/gateway/src/utils_test.rs @@ -0,0 +1,66 @@ +use pretty_assertions::assert_eq; +use rstest::rstest; + +use crate::utils::is_subsequence; + +#[rstest] +#[case::empty(&[], &[], true)] +#[case::empty_subsequence(&[], &["a".to_string(), "b".to_string()], true)] +#[case::empty_sequence(&["a".to_string()], &[], false)] +#[case::subsequence_1( + &["a".to_string()], + &["a".to_string(), "b".to_string(), "c".to_string()], + true +)] +#[case::subsequence_1( + &["b".to_string()], + &["a".to_string(), "b".to_string(), "c".to_string()], + true +)] +#[case::subsequence_1( + &["c".to_string()], + &["a".to_string(), "b".to_string(), "c".to_string()], + true +)] +#[case::subsequence_4( + &["a".to_string(), "b".to_string()], + &["a".to_string(), "b".to_string(), "c".to_string()], + true +)] +#[case::subsequence_5( + &["a".to_string(), "c".to_string()], + &["a".to_string(), "b".to_string(), "c".to_string()], + true +)] +#[case::subsequence_6( + &["b".to_string(), "c".to_string()], + &["a".to_string(), "b".to_string(), "c".to_string()], + true +)] +#[case::subsequence_7( + &["a".to_string(), "b".to_string(), "c".to_string()], + &["a".to_string(), "b".to_string(), "c".to_string()], + true +)] +#[case::out_of_order_1( + &["b".to_string(), "a".to_string()], + &["a".to_string(), "b".to_string(), "c".to_string()], + false +)] +#[case::out_of_order_2( + &["b".to_string(), "a".to_string(), "c".to_string()], + &["a".to_string(), "b".to_string(), "c".to_string()], + false +)] +#[case::unrelated( + &["a".to_string(), "b".to_string(), "d".to_string()], + &["a".to_string(), "b".to_string(), "c".to_string()], + false +)] +fn test_is_subsequence( + #[case] subsequence: &[String], + #[case] sequence: &[String], + #[case] expected_result: bool, +) { + assert_eq!(is_subsequence(subsequence, sequence), expected_result); +}