Skip to content

Commit 4e760df

Browse files
committed
feat: check that the compiled class hash matches the supplied class
1 parent 5419863 commit 4e760df

File tree

6 files changed

+73
-4
lines changed

6 files changed

+73
-4
lines changed

crates/gateway/src/errors.rs

+6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use blockifier::transaction::errors::TransactionExecutionError;
77
use cairo_vm::types::errors::program_errors::ProgramError;
88
use serde_json::{Error as SerdeError, Value};
99
use starknet_api::block::{BlockNumber, GasPrice};
10+
use starknet_api::core::CompiledClassHash;
1011
use starknet_api::transaction::{Resource, ResourceBounds};
1112
use starknet_api::StarknetApiError;
1213
use thiserror::Error;
@@ -19,6 +20,11 @@ use crate::compiler_version::{VersionId, VersionIdError};
1920
pub enum GatewayError {
2021
#[error(transparent)]
2122
CompilationError(#[from] starknet_sierra_compile::compile::CompilationUtilError),
23+
#[error(
24+
"The supplied compiled class hash {supplied:?} does not match the hash of the Casm class \
25+
compiled from the supplied Sierra {hash_result:?}."
26+
)]
27+
CompiledClassHashMismatch { supplied: CompiledClassHash, hash_result: CompiledClassHash },
2228
#[error(transparent)]
2329
DeclaredContractClassError(#[from] ContractClassError),
2430
#[error(transparent)]

crates/gateway/src/gateway.rs

+11
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ use axum::extract::State;
77
use axum::routing::{get, post};
88
use axum::{Json, Router};
99
use blockifier::execution::contract_class::{ClassInfo, ContractClass, ContractClassV1};
10+
use blockifier::execution::execution_utils::felt_to_stark_felt;
11+
use starknet_api::core::CompiledClassHash;
1012
use starknet_api::rpc_transaction::{RPCDeclareTransaction, RPCTransaction};
1113
use starknet_api::transaction::TransactionHash;
1214
use starknet_mempool_types::communication::SharedMempoolClient;
@@ -157,6 +159,15 @@ pub fn compile_contract_class(declare_tx: &RPCDeclareTransaction) -> GatewayResu
157159
}
158160
};
159161

162+
let hash_result =
163+
CompiledClassHash(felt_to_stark_felt(&casm_contract_class.compiled_class_hash()));
164+
if hash_result != tx.compiled_class_hash {
165+
return Err(GatewayError::CompiledClassHashMismatch {
166+
supplied: tx.compiled_class_hash,
167+
hash_result,
168+
});
169+
}
170+
160171
// Convert Casm contract class to Starknet contract class directly.
161172
let blockifier_contract_class =
162173
ContractClass::V1(ContractClassV1::try_from(casm_contract_class)?);

crates/gateway/src/gateway_test.rs

+46-1
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
use std::sync::Arc;
22

3+
use assert_matches::assert_matches;
34
use axum::body::{Bytes, HttpBody};
45
use axum::extract::State;
56
use axum::http::StatusCode;
67
use axum::response::{IntoResponse, Response};
78
use blockifier::context::ChainInfo;
9+
use blockifier::execution::contract_class::ContractClass;
810
use blockifier::test_utils::CairoVersion;
911
use rstest::{fixture, rstest};
10-
use starknet_api::rpc_transaction::RPCTransaction;
12+
use starknet_api::core::CompiledClassHash;
13+
use starknet_api::rpc_transaction::{RPCDeclareTransaction, RPCTransaction};
1114
use starknet_api::transaction::TransactionHash;
1215
use starknet_mempool::communication::create_mempool_server;
1316
use starknet_mempool::mempool::Mempool;
@@ -16,6 +19,7 @@ use tokio::sync::mpsc::channel;
1619
use tokio::task;
1720

1821
use crate::config::{StatefulTransactionValidatorConfig, StatelessTransactionValidatorConfig};
22+
use crate::errors::GatewayError;
1923
use crate::gateway::{add_tx, compile_contract_class, AppState, SharedMempoolClient};
2024
use crate::starknet_api_test_utils::{declare_tx, deploy_account_tx, invoke_tx};
2125
use crate::state_reader_test_utils::{
@@ -100,6 +104,47 @@ async fn test_add_tx(
100104
assert_eq!(tx_hash, serde_json::from_slice(response_bytes).unwrap());
101105
}
102106

107+
#[test]
108+
fn test_compile_contract_class_failure() {
109+
let mut declare_tx_v3 = match declare_tx() {
110+
RPCTransaction::Declare(RPCDeclareTransaction::V3(declare_tx)) => declare_tx,
111+
_ => panic!("Invalid transaction type"),
112+
};
113+
let expected_hash_result = declare_tx_v3.compiled_class_hash;
114+
let supplied_hash = CompiledClassHash::default();
115+
116+
declare_tx_v3.compiled_class_hash = supplied_hash;
117+
let declare_tx = RPCDeclareTransaction::V3(declare_tx_v3);
118+
119+
let result = compile_contract_class(&declare_tx);
120+
assert_matches!(
121+
result.unwrap_err(),
122+
GatewayError::CompiledClassHashMismatch { supplied, hash_result }
123+
if supplied == supplied_hash && hash_result == expected_hash_result
124+
);
125+
}
126+
127+
#[test]
128+
fn test_compile_contract_class() {
129+
let declare_tx = match declare_tx() {
130+
RPCTransaction::Declare(declare_tx) => declare_tx,
131+
_ => panic!("Invalid transaction type"),
132+
};
133+
let RPCDeclareTransaction::V3(declare_tx_v3) = &declare_tx;
134+
let contract_class = &declare_tx_v3.contract_class;
135+
136+
let result = compile_contract_class(&declare_tx);
137+
assert_matches!(
138+
result,
139+
Ok(class_info)
140+
if (
141+
matches!(class_info.contract_class(), ContractClass::V1(_))
142+
&& class_info.sierra_program_length() == contract_class.sierra_program.len()
143+
&& class_info.abi_length() == contract_class.abi.len()
144+
)
145+
);
146+
}
147+
103148
async fn to_bytes(res: Response) -> Bytes {
104149
res.into_body().collect().await.unwrap().to_bytes()
105150
}

crates/gateway/src/starknet_api_test_utils.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ use starknet_api::transaction::{
1818
TransactionSignature, TransactionVersion,
1919
};
2020
use starknet_api::{calldata, stark_felt};
21-
use test_utils::{get_absolute_path, CONTRACT_CLASS_FILE, TEST_FILES_FOLDER};
21+
use test_utils::{
22+
get_absolute_path, COMPILED_CLASS_HASH_OF_CONTRACT_CLASS, CONTRACT_CLASS_FILE,
23+
TEST_FILES_FOLDER,
24+
};
2225

2326
use crate::{declare_tx_args, deploy_account_tx_args, invoke_tx_args};
2427

@@ -97,6 +100,7 @@ pub fn declare_tx() -> RPCTransaction {
97100
env::set_current_dir(get_absolute_path(TEST_FILES_FOLDER)).expect("Couldn't set working dir.");
98101
let json_file_path = Path::new(CONTRACT_CLASS_FILE);
99102
let contract_class = serde_json::from_reader(File::open(json_file_path).unwrap()).unwrap();
103+
let compiled_class_hash = CompiledClassHash(stark_felt!(COMPILED_CLASS_HASH_OF_CONTRACT_CLASS));
100104

101105
let cairo_version = CairoVersion::Cairo1;
102106
let account_contract = FeatureContract::AccountWithoutValidations(cairo_version);
@@ -109,7 +113,8 @@ pub fn declare_tx() -> RPCTransaction {
109113
sender_address: account_address,
110114
resource_bounds: executable_resource_bounds_mapping(),
111115
nonce,
112-
contract_class
116+
class_hash: compiled_class_hash,
117+
contract_class,
113118
))
114119
}
115120

crates/gateway/src/stateful_transaction_validator_test.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ use crate::stateful_transaction_validator::StatefulTransactionValidator;
4646
declare_tx(),
4747
local_test_state_reader_factory(CairoVersion::Cairo1, false),
4848
Ok(TransactionHash(StarkFelt::try_from(
49-
"0x0278ed2700d5a30254a6b895d4e1140438d7d1a3b2b2ce0c096a9d5ee1c61f39"
49+
"0x02da54b89e00d2e201f8e3ed2bcc715a69e89aefdce88aff2d2facb8dec55c0a"
5050
).unwrap()))
5151
)]
5252
#[case::invalid_tx(

crates/test_utils/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ use std::path::{Path, PathBuf};
33

44
pub const TEST_FILES_FOLDER: &str = "crates/test_utils/test_files";
55
pub const CONTRACT_CLASS_FILE: &str = "contract_class.json";
6+
pub const COMPILED_CLASS_HASH_OF_CONTRACT_CLASS: &str =
7+
"0x01e4f1248860f32c336f93f2595099aaa4959be515e40b75472709ef5243ae17";
68

79
/// Returns the absolute path from the project root.
810
pub fn get_absolute_path(relative_path: &str) -> PathBuf {

0 commit comments

Comments
 (0)